mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 03:32:00 +08:00
feat: 优化网页消息提醒样式
This commit is contained in:
@@ -1,11 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import 'vue-sonner/style.css'
|
import 'vue-sonner/style.css'
|
||||||
import { KeepAlive, onMounted, watch } from 'vue'
|
import '@/sonner-overrides.css'
|
||||||
|
import { computed, KeepAlive, onMounted, watch } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import { RouterView, useRouter } from 'vue-router'
|
import { RouterView, useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useSystemStore } from '@/stores/system'
|
import { useSystemStore } from '@/stores/system'
|
||||||
import { Toaster } from '@/components/ui/sonner'
|
import { Toaster } from '@/components/ui/sonner'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
/** Defaults merged into every toast; duration also set on `<Toaster>` for clarity */
|
||||||
|
const toasterToastOptions = computed(() => ({
|
||||||
|
closeButtonAriaLabel: t('toast.closeNotification'),
|
||||||
|
classes: {
|
||||||
|
title: 'text-sm font-semibold leading-snug tracking-tight text-popover-foreground',
|
||||||
|
description: 'text-sm leading-relaxed text-muted-foreground',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const systemStore = useSystemStore()
|
const systemStore = useSystemStore()
|
||||||
@@ -56,5 +69,18 @@ watch(
|
|||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
<component :is="Component" v-if="route.name !== 'Console' || !authStore.isAuthenticated" />
|
<component :is="Component" v-if="route.name !== 'Console' || !authStore.isAuthenticated" />
|
||||||
</RouterView>
|
</RouterView>
|
||||||
<Toaster rich-colors close-button position="top-center" />
|
<Toaster
|
||||||
|
rich-colors
|
||||||
|
close-button
|
||||||
|
position="top-center"
|
||||||
|
close-button-position="top-right"
|
||||||
|
theme="system"
|
||||||
|
:duration="4000"
|
||||||
|
:gap="14"
|
||||||
|
:visible-toasts="3"
|
||||||
|
:offset="{ top: '1rem', right: '1rem', left: '1rem', bottom: '1rem' }"
|
||||||
|
:mobile-offset="{ top: 'max(1rem, env(safe-area-inset-top))', bottom: 'max(1rem, env(safe-area-inset-bottom))', left: '1rem', right: '1rem' }"
|
||||||
|
:toast-options="toasterToastOptions"
|
||||||
|
:container-aria-label="t('toast.notificationsRegion')"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { toast } from 'vue-sonner'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
@@ -122,8 +121,6 @@ async function applyConfig() {
|
|||||||
}
|
}
|
||||||
unifiedAudio.disconnect()
|
unifiedAudio.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success(t('config.applied'))
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.info('[AudioConfig] Failed to apply config:', e)
|
console.info('[AudioConfig] Failed to apply config:', e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -202,8 +202,6 @@ async function applyHidConfig() {
|
|||||||
|
|
||||||
await configStore.updateHid(config)
|
await configStore.updateHid(config)
|
||||||
|
|
||||||
toast.success(t('config.applied'))
|
|
||||||
|
|
||||||
// HID state will be updated via WebSocket device_info event
|
// HID state will be updated via WebSocket device_info event
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.info('[HidConfig] Failed to apply config:', e)
|
console.info('[HidConfig] Failed to apply config:', e)
|
||||||
|
|||||||
@@ -207,7 +207,6 @@ async function connectImage(image: MsdImage) {
|
|||||||
try {
|
try {
|
||||||
await msdApi.connect('image', image.id, cdromMode.value, readOnly.value)
|
await msdApi.connect('image', image.id, cdromMode.value, readOnly.value)
|
||||||
await systemStore.fetchMsdState()
|
await systemStore.fetchMsdState()
|
||||||
toast.success(t('msd.imageMounted', { name: image.name }))
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to connect image:', e)
|
console.error('Failed to connect image:', e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -225,7 +224,6 @@ async function connectDrive() {
|
|||||||
try {
|
try {
|
||||||
await msdApi.connect('drive')
|
await msdApi.connect('drive')
|
||||||
await systemStore.fetchMsdState()
|
await systemStore.fetchMsdState()
|
||||||
toast.success(t('common.connected'))
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to connect drive:', e)
|
console.error('Failed to connect drive:', e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -242,7 +240,6 @@ async function disconnect() {
|
|||||||
try {
|
try {
|
||||||
await msdApi.disconnect()
|
await msdApi.disconnect()
|
||||||
await systemStore.fetchMsdState()
|
await systemStore.fetchMsdState()
|
||||||
toast.success(t('msd.disconnected'))
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to disconnect:', e)
|
console.error('Failed to disconnect:', e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -263,11 +260,9 @@ async function executeDelete() {
|
|||||||
if (deleteTarget.value.type === 'image') {
|
if (deleteTarget.value.type === 'image') {
|
||||||
await msdApi.deleteImage(deleteTarget.value.id)
|
await msdApi.deleteImage(deleteTarget.value.id)
|
||||||
images.value = images.value.filter(i => i.id !== deleteTarget.value!.id)
|
images.value = images.value.filter(i => i.id !== deleteTarget.value!.id)
|
||||||
toast.success(t('common.success'))
|
|
||||||
} else {
|
} else {
|
||||||
await msdApi.deleteDriveFile(deleteTarget.value.id)
|
await msdApi.deleteDriveFile(deleteTarget.value.id)
|
||||||
await loadDriveFiles()
|
await loadDriveFiles()
|
||||||
toast.success(t('common.success'))
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to delete:', e)
|
console.error('Failed to delete:', e)
|
||||||
@@ -313,7 +308,6 @@ async function createDrive() {
|
|||||||
await loadDriveInfo()
|
await loadDriveInfo()
|
||||||
await loadDriveFiles()
|
await loadDriveFiles()
|
||||||
showDriveInitDialog.value = false
|
showDriveInitDialog.value = false
|
||||||
toast.success(t('common.success'))
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to initialize drive:', e)
|
console.error('Failed to initialize drive:', e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -330,7 +324,6 @@ async function deleteDrive() {
|
|||||||
driveFiles.value = []
|
driveFiles.value = []
|
||||||
currentPath.value = '/'
|
currentPath.value = '/'
|
||||||
showDeleteDriveDialog.value = false
|
showDeleteDriveDialog.value = false
|
||||||
toast.success(t('msd.driveDeleted'))
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to delete drive:', e)
|
console.error('Failed to delete drive:', e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -525,7 +525,6 @@ async function applyVideoConfig() {
|
|||||||
fps: toConfigFps(selectedFps.value),
|
fps: toConfigFps(selectedFps.value),
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.success(t('config.applied'))
|
|
||||||
isDirty.value = false
|
isDirty.value = false
|
||||||
// Stream state will be updated via WebSocket system.device_info event
|
// Stream state will be updated via WebSocket system.device_info event
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const props = defineProps<ToasterProps>()
|
|||||||
'--normal-bg': 'var(--popover)',
|
'--normal-bg': 'var(--popover)',
|
||||||
'--normal-text': 'var(--popover-foreground)',
|
'--normal-text': 'var(--popover-foreground)',
|
||||||
'--normal-border': 'var(--border)',
|
'--normal-border': 'var(--border)',
|
||||||
'--border-radius': 'var(--radius)',
|
'--border-radius': 'calc(var(--radius) + 0.1875rem)',
|
||||||
}"
|
}"
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
>
|
>
|
||||||
@@ -36,7 +36,7 @@ const props = defineProps<ToasterProps>()
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #close-icon>
|
<template #close-icon>
|
||||||
<XIcon class="size-4" />
|
<XIcon class="size-3 shrink-0" />
|
||||||
</template>
|
</template>
|
||||||
</Sonner>
|
</Sonner>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
|
|
||||||
import { ref, watch, type Ref } from 'vue'
|
import { ref, watch, type Ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
import { toast } from 'vue-sonner'
|
|
||||||
|
|
||||||
export interface UseConfigPopoverOptions {
|
export interface UseConfigPopoverOptions {
|
||||||
/** Reactive open state from props */
|
/** Reactive open state from props */
|
||||||
@@ -13,8 +11,6 @@ export interface UseConfigPopoverOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useConfigPopover(options: UseConfigPopoverOptions) {
|
export function useConfigPopover(options: UseConfigPopoverOptions) {
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const applying = ref(false)
|
const applying = ref(false)
|
||||||
const loadingDevices = ref(false)
|
const loadingDevices = ref(false)
|
||||||
|
|
||||||
@@ -36,7 +32,6 @@ export function useConfigPopover(options: UseConfigPopoverOptions) {
|
|||||||
applying.value = true
|
applying.value = true
|
||||||
try {
|
try {
|
||||||
await applyFn()
|
await applyFn()
|
||||||
toast.success(t('config.applied'))
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.info('[ConfigPopover] Apply failed:', e)
|
console.info('[ConfigPopover] Apply failed:', e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -43,12 +43,6 @@ export function useConsoleEvents(handlers: ConsoleEventHandlers) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleStreamReconnecting(data: { device: string; attempt: number }) {
|
function handleStreamReconnecting(data: { device: string; attempt: number }) {
|
||||||
if (data.attempt === 1 || data.attempt % 5 === 0) {
|
|
||||||
toast.info(t('console.deviceRecovering'), {
|
|
||||||
description: t('console.deviceRecoveringDesc', { attempt: data.attempt }),
|
|
||||||
duration: 3000,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
handlers.onStreamReconnecting?.(data)
|
handlers.onStreamReconnecting?.(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,10 +50,6 @@ export function useConsoleEvents(handlers: ConsoleEventHandlers) {
|
|||||||
if (systemStore.stream) {
|
if (systemStore.stream) {
|
||||||
systemStore.stream.online = true
|
systemStore.stream.online = true
|
||||||
}
|
}
|
||||||
toast.success(t('console.deviceRecovered'), {
|
|
||||||
description: t('console.deviceRecoveredDesc'),
|
|
||||||
duration: 3000,
|
|
||||||
})
|
|
||||||
handlers.onStreamRecovered?.(_data)
|
handlers.onStreamRecovered?.(_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ export default {
|
|||||||
toggleLanguage: 'Toggle language',
|
toggleLanguage: 'Toggle language',
|
||||||
retry: 'Retry',
|
retry: 'Retry',
|
||||||
},
|
},
|
||||||
|
toast: {
|
||||||
|
closeNotification: 'Dismiss notification',
|
||||||
|
notificationsRegion: 'Notifications',
|
||||||
|
},
|
||||||
api: {
|
api: {
|
||||||
operationFailed: 'Operation Failed',
|
operationFailed: 'Operation Failed',
|
||||||
operationFailedDesc: 'Operation failed',
|
operationFailedDesc: 'Operation failed',
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ export default {
|
|||||||
toggleLanguage: '切换语言',
|
toggleLanguage: '切换语言',
|
||||||
retry: '重试',
|
retry: '重试',
|
||||||
},
|
},
|
||||||
|
toast: {
|
||||||
|
closeNotification: '关闭通知',
|
||||||
|
notificationsRegion: '通知',
|
||||||
|
},
|
||||||
api: {
|
api: {
|
||||||
operationFailed: '操作失败',
|
operationFailed: '操作失败',
|
||||||
operationFailedDesc: '操作失败',
|
operationFailedDesc: '操作失败',
|
||||||
|
|||||||
205
web/src/sonner-overrides.css
Normal file
205
web/src/sonner-overrides.css
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
/* Must load after vue-sonner/style.css — overrides Sonner defaults to match shadcn-style tokens */
|
||||||
|
|
||||||
|
[data-sonner-toaster] {
|
||||||
|
font-family: inherit;
|
||||||
|
/* Inline default is 356px — flatten bar reads better a bit wider */
|
||||||
|
--width: min(30rem, calc(100vw - 2rem)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Top-right preset: tuck close control inside (+ vertically center) instead of protruding outside */
|
||||||
|
[data-sonner-toaster] [data-close-button-position='top-right'] {
|
||||||
|
--toast-close-button-right: 0.5rem;
|
||||||
|
--toast-close-button-left: unset;
|
||||||
|
--toast-close-button-top: 50%;
|
||||||
|
--toast-close-button-bottom: unset;
|
||||||
|
--toast-close-button-transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast][data-styled='true'] {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
gap: 0.5rem;
|
||||||
|
box-shadow:
|
||||||
|
0 1px 3px rgb(0 0 0 / 0.06),
|
||||||
|
0 1px 2px rgb(0 0 0 / 0.04);
|
||||||
|
padding: 0.5625rem 2.125rem 0.5625rem 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast][data-styled='true'] [data-content] {
|
||||||
|
gap: 0.0625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(.dark *) [data-sonner-toast][data-styled='true'] {
|
||||||
|
box-shadow:
|
||||||
|
0 1px 3px rgb(0 0 0 / 0.28),
|
||||||
|
0 1px 2px rgb(0 0 0 / 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast]:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow:
|
||||||
|
0 1px 3px rgb(0 0 0 / 0.06),
|
||||||
|
0 1px 2px rgb(0 0 0 / 0.04),
|
||||||
|
0 0 0 2px var(--background),
|
||||||
|
0 0 0 4px color-mix(in oklch, var(--ring) 55%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(.dark *) [data-sonner-toast]:focus-visible {
|
||||||
|
box-shadow:
|
||||||
|
0 1px 3px rgb(0 0 0 / 0.28),
|
||||||
|
0 1px 2px rgb(0 0 0 / 0.2),
|
||||||
|
0 0 0 2px var(--background),
|
||||||
|
0 0 0 4px color-mix(in oklch, var(--ring) 55%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
html body [data-sonner-toast][data-styled='true'] [data-title] {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
html body [data-sonner-toast][data-styled='true'] [data-description] {
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only semantic variants keep description tied to tinted foreground */
|
||||||
|
html body [data-rich-colors='true'][data-sonner-toast][data-type='error'][data-styled='true'] [data-description],
|
||||||
|
html body [data-rich-colors='true'][data-sonner-toast][data-type='warning'][data-styled='true'] [data-description],
|
||||||
|
html body [data-rich-colors='true'][data-sonner-toast][data-type='success'][data-styled='true'] [data-description],
|
||||||
|
html body [data-rich-colors='true'][data-sonner-toast][data-type='info'][data-styled='true'] [data-description] {
|
||||||
|
color: inherit;
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compact ghost close (inside strip, smaller hit target) */
|
||||||
|
[data-sonner-toast][data-styled='true'] [data-close-button] {
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
min-height: 1.5rem;
|
||||||
|
min-width: 1.5rem;
|
||||||
|
border-radius: calc(var(--radius) - 4px);
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast][data-styled='true'] [data-close-button]:hover {
|
||||||
|
background: color-mix(in oklch, var(--muted) 65%, transparent);
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast][data-styled='true']:hover [data-close-button]:hover {
|
||||||
|
background: color-mix(in oklch, var(--muted) 65%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast][data-styled='true'] [data-close-button]:focus-visible {
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px var(--background),
|
||||||
|
0 0 0 4px color-mix(in oklch, var(--ring) 50%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Semantic: soft tinted surface + theme tokens */
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='error'][data-styled='true'] {
|
||||||
|
background: color-mix(in oklch, var(--destructive) 14%, var(--popover));
|
||||||
|
border-color: color-mix(in oklch, var(--destructive) 38%, var(--border));
|
||||||
|
color: var(--destructive);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='error'][data-styled='true'] [data-close-button] {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
opacity: 0.88;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='error'][data-styled='true'] [data-close-button]:hover {
|
||||||
|
background: color-mix(in oklch, var(--destructive) 16%, transparent);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='warning'][data-styled='true'] {
|
||||||
|
background: color-mix(in oklch, var(--chart-4) 16%, var(--popover));
|
||||||
|
border-color: color-mix(in oklch, var(--chart-4) 36%, var(--border));
|
||||||
|
color: color-mix(in oklch, var(--chart-4) 48%, var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='warning'][data-styled='true'] [data-close-button] {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
opacity: 0.88;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='warning'][data-styled='true'] [data-close-button]:hover {
|
||||||
|
background: color-mix(in oklch, var(--chart-4) 18%, transparent);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='success'][data-styled='true'] {
|
||||||
|
background: color-mix(in oklch, var(--chart-2) 14%, var(--popover));
|
||||||
|
border-color: color-mix(in oklch, var(--chart-2) 34%, var(--border));
|
||||||
|
color: color-mix(in oklch, var(--chart-2) 42%, var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='success'][data-styled='true'] [data-close-button] {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
opacity: 0.88;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='success'][data-styled='true'] [data-close-button]:hover {
|
||||||
|
background: color-mix(in oklch, var(--chart-2) 16%, transparent);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='info'][data-styled='true'] {
|
||||||
|
background: color-mix(in oklch, var(--chart-1) 14%, var(--popover));
|
||||||
|
border-color: color-mix(in oklch, var(--chart-1) 34%, var(--border));
|
||||||
|
color: color-mix(in oklch, var(--chart-1) 42%, var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='info'][data-styled='true'] [data-close-button] {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
opacity: 0.88;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-rich-colors='true'][data-sonner-toast][data-type='info'][data-styled='true'] [data-close-button]:hover {
|
||||||
|
background: color-mix(in oklch, var(--chart-1) 16%, transparent);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast][data-styled='true'] [data-button]:not([data-cancel]) {
|
||||||
|
border-radius: calc(var(--radius) - 2px);
|
||||||
|
height: 2rem;
|
||||||
|
padding-inline: 0.75rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--primary);
|
||||||
|
color: var(--primary-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast][data-styled='true'] [data-button]:not([data-cancel]):focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast][data-styled='true'] [data-cancel] {
|
||||||
|
border-radius: calc(var(--radius) - 2px);
|
||||||
|
height: 2rem;
|
||||||
|
padding-inline: 0.75rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--foreground);
|
||||||
|
background: color-mix(in oklch, var(--muted) 88%, transparent);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast][data-styled='true'] [data-cancel]:hover {
|
||||||
|
background: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sonner-loading-bar {
|
||||||
|
background: var(--muted-foreground);
|
||||||
|
}
|
||||||
@@ -835,7 +835,7 @@ async function handleAudioStateChanged(data: { streaming: boolean; device: strin
|
|||||||
await unifiedAudio.connect()
|
await unifiedAudio.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStreamConfigChanging(data: any) {
|
function handleStreamConfigChanging(_data: any) {
|
||||||
if (retryTimeoutId !== null) {
|
if (retryTimeoutId !== null) {
|
||||||
clearTimeout(retryTimeoutId)
|
clearTimeout(retryTimeoutId)
|
||||||
retryTimeoutId = null
|
retryTimeoutId = null
|
||||||
@@ -853,14 +853,9 @@ function handleStreamConfigChanging(data: any) {
|
|||||||
consecutiveErrors = 0
|
consecutiveErrors = 0
|
||||||
|
|
||||||
backendFps.value = 0
|
backendFps.value = 0
|
||||||
|
|
||||||
toast.info(t('console.videoRestarting'), {
|
|
||||||
description: data.reason === 'device_switch' ? t('console.deviceSwitching') : t('console.configChanging'),
|
|
||||||
duration: 5000,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleStreamConfigApplied(data: any) {
|
async function handleStreamConfigApplied(_data: any) {
|
||||||
consecutiveErrors = 0
|
consecutiveErrors = 0
|
||||||
|
|
||||||
gracePeriodTimeoutId = window.setTimeout(() => {
|
gracePeriodTimeoutId = window.setTimeout(() => {
|
||||||
@@ -882,11 +877,6 @@ async function handleStreamConfigApplied(data: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
videoRestarting.value = false
|
videoRestarting.value = false
|
||||||
|
|
||||||
toast.success(t('console.videoRestarted'), {
|
|
||||||
description: `${data.device} - ${data.resolution[0]}x${data.resolution[1]} @ ${formatFpsValue(data.fps)}fps`,
|
|
||||||
duration: 3000,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleWebRTCReady(data: { codec: string; hardware: boolean; transition_id?: string }) {
|
function handleWebRTCReady(data: { codec: string; hardware: boolean; transition_id?: string }) {
|
||||||
@@ -1158,11 +1148,6 @@ function handleStreamModeChanged(data: { mode: string; previous_mode: string })
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.info(t('console.streamModeChanged'), {
|
|
||||||
description: t('console.streamModeChangedDesc', { mode: data.mode.toUpperCase() }),
|
|
||||||
duration: 5000,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (newMode !== videoMode.value) {
|
if (newMode !== videoMode.value) {
|
||||||
syncToServerMode(newMode)
|
syncToServerMode(newMode)
|
||||||
}
|
}
|
||||||
@@ -1238,11 +1223,6 @@ async function connectWebRTCOnly(codec: VideoMode = 'h264') {
|
|||||||
try {
|
try {
|
||||||
const success = await connectWebRTCSerial('connectWebRTCOnly')
|
const success = await connectWebRTCSerial('connectWebRTCOnly')
|
||||||
if (success) {
|
if (success) {
|
||||||
toast.success(t('console.webrtcConnected'), {
|
|
||||||
description: t('console.webrtcConnectedDesc'),
|
|
||||||
duration: 3000,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Force video rebind even when the track already exists
|
// Force video rebind even when the track already exists
|
||||||
// This fixes missing video after returning to the page
|
// This fixes missing video after returning to the page
|
||||||
await rebindWebRTCVideo()
|
await rebindWebRTCVideo()
|
||||||
@@ -1338,11 +1318,6 @@ async function switchToWebRTC(codec: VideoMode = 'h264') {
|
|||||||
success = await connectWebRTCSerial('switchToWebRTC')
|
success = await connectWebRTCSerial('switchToWebRTC')
|
||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
toast.success(t('console.webrtcConnected'), {
|
|
||||||
description: t('console.webrtcConnectedDesc'),
|
|
||||||
duration: 3000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await rebindWebRTCVideo()
|
await rebindWebRTCVideo()
|
||||||
|
|
||||||
videoLoading.value = false
|
videoLoading.value = false
|
||||||
@@ -1570,7 +1545,6 @@ async function handleChangePassword() {
|
|||||||
changingPassword.value = true
|
changingPassword.value = true
|
||||||
try {
|
try {
|
||||||
await authApi.changePassword(currentPassword.value, newPassword.value)
|
await authApi.changePassword(currentPassword.value, newPassword.value)
|
||||||
toast.success(t('auth.passwordChanged'))
|
|
||||||
|
|
||||||
currentPassword.value = ''
|
currentPassword.value = ''
|
||||||
newPassword.value = ''
|
newPassword.value = ''
|
||||||
@@ -1619,7 +1593,6 @@ async function handleReset() {
|
|||||||
async function handleWol(mac: string) {
|
async function handleWol(mac: string) {
|
||||||
try {
|
try {
|
||||||
await atxConfigApi.sendWol(mac)
|
await atxConfigApi.sendWol(mac)
|
||||||
toast.success(t('atx.wolSent'))
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(t('atx.wolFailed'))
|
toast.error(t('atx.wolFailed'))
|
||||||
}
|
}
|
||||||
@@ -1684,10 +1657,6 @@ function handleKeyDown(e: KeyboardEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isFullscreen.value && (e.metaKey || e.key === 'Meta')) {
|
if (!isFullscreen.value && (e.metaKey || e.key === 'Meta')) {
|
||||||
toast.info(t('console.metaKeyHint'), {
|
|
||||||
description: t('console.metaKeyHintDesc'),
|
|
||||||
duration: 3000,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const canonicalKey = keyboardEventToCanonicalKey(e.code, e.key)
|
const canonicalKey = keyboardEventToCanonicalKey(e.code, e.key)
|
||||||
@@ -2039,10 +2008,6 @@ function handlePointerLockChange() {
|
|||||||
localCrosshairPos.value = { x: r.width / 2, y: r.height / 2 }
|
localCrosshairPos.value = { x: r.width / 2, y: r.height / 2 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toast.info(t('console.pointerLocked'), {
|
|
||||||
description: t('console.pointerLockedDesc'),
|
|
||||||
duration: 3000,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2207,10 +2172,6 @@ function handleToggleMouseMode() {
|
|||||||
mousePosition.value = { x: 0, y: 0 }
|
mousePosition.value = { x: 0, y: 0 }
|
||||||
|
|
||||||
if (mouseMode.value === 'relative') {
|
if (mouseMode.value === 'relative') {
|
||||||
toast.info(t('console.relativeModeHint'), {
|
|
||||||
description: t('console.relativeModeHintDesc'),
|
|
||||||
duration: 5000,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user