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">
|
||||
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 { useAuthStore } from '@/stores/auth'
|
||||
import { useSystemStore } from '@/stores/system'
|
||||
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 authStore = useAuthStore()
|
||||
const systemStore = useSystemStore()
|
||||
@@ -56,5 +69,18 @@ watch(
|
||||
</KeepAlive>
|
||||
<component :is="Component" v-if="route.name !== 'Console' || !authStore.isAuthenticated" />
|
||||
</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>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
@@ -122,8 +121,6 @@ async function applyConfig() {
|
||||
}
|
||||
unifiedAudio.disconnect()
|
||||
}
|
||||
|
||||
toast.success(t('config.applied'))
|
||||
} catch (e) {
|
||||
console.info('[AudioConfig] Failed to apply config:', e)
|
||||
} finally {
|
||||
|
||||
@@ -202,8 +202,6 @@ async function applyHidConfig() {
|
||||
|
||||
await configStore.updateHid(config)
|
||||
|
||||
toast.success(t('config.applied'))
|
||||
|
||||
// HID state will be updated via WebSocket device_info event
|
||||
} catch (e) {
|
||||
console.info('[HidConfig] Failed to apply config:', e)
|
||||
|
||||
@@ -207,7 +207,6 @@ async function connectImage(image: MsdImage) {
|
||||
try {
|
||||
await msdApi.connect('image', image.id, cdromMode.value, readOnly.value)
|
||||
await systemStore.fetchMsdState()
|
||||
toast.success(t('msd.imageMounted', { name: image.name }))
|
||||
} catch (e) {
|
||||
console.error('Failed to connect image:', e)
|
||||
} finally {
|
||||
@@ -225,7 +224,6 @@ async function connectDrive() {
|
||||
try {
|
||||
await msdApi.connect('drive')
|
||||
await systemStore.fetchMsdState()
|
||||
toast.success(t('common.connected'))
|
||||
} catch (e) {
|
||||
console.error('Failed to connect drive:', e)
|
||||
} finally {
|
||||
@@ -242,7 +240,6 @@ async function disconnect() {
|
||||
try {
|
||||
await msdApi.disconnect()
|
||||
await systemStore.fetchMsdState()
|
||||
toast.success(t('msd.disconnected'))
|
||||
} catch (e) {
|
||||
console.error('Failed to disconnect:', e)
|
||||
} finally {
|
||||
@@ -263,11 +260,9 @@ async function executeDelete() {
|
||||
if (deleteTarget.value.type === 'image') {
|
||||
await msdApi.deleteImage(deleteTarget.value.id)
|
||||
images.value = images.value.filter(i => i.id !== deleteTarget.value!.id)
|
||||
toast.success(t('common.success'))
|
||||
} else {
|
||||
await msdApi.deleteDriveFile(deleteTarget.value.id)
|
||||
await loadDriveFiles()
|
||||
toast.success(t('common.success'))
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to delete:', e)
|
||||
@@ -313,7 +308,6 @@ async function createDrive() {
|
||||
await loadDriveInfo()
|
||||
await loadDriveFiles()
|
||||
showDriveInitDialog.value = false
|
||||
toast.success(t('common.success'))
|
||||
} catch (e) {
|
||||
console.error('Failed to initialize drive:', e)
|
||||
} finally {
|
||||
@@ -330,7 +324,6 @@ async function deleteDrive() {
|
||||
driveFiles.value = []
|
||||
currentPath.value = '/'
|
||||
showDeleteDriveDialog.value = false
|
||||
toast.success(t('msd.driveDeleted'))
|
||||
} catch (e) {
|
||||
console.error('Failed to delete drive:', e)
|
||||
} finally {
|
||||
|
||||
@@ -525,7 +525,6 @@ async function applyVideoConfig() {
|
||||
fps: toConfigFps(selectedFps.value),
|
||||
})
|
||||
|
||||
toast.success(t('config.applied'))
|
||||
isDirty.value = false
|
||||
// Stream state will be updated via WebSocket system.device_info event
|
||||
} catch (e) {
|
||||
|
||||
@@ -14,7 +14,7 @@ const props = defineProps<ToasterProps>()
|
||||
'--normal-bg': 'var(--popover)',
|
||||
'--normal-text': 'var(--popover-foreground)',
|
||||
'--normal-border': 'var(--border)',
|
||||
'--border-radius': 'var(--radius)',
|
||||
'--border-radius': 'calc(var(--radius) + 0.1875rem)',
|
||||
}"
|
||||
v-bind="props"
|
||||
>
|
||||
@@ -36,7 +36,7 @@ const props = defineProps<ToasterProps>()
|
||||
</div>
|
||||
</template>
|
||||
<template #close-icon>
|
||||
<XIcon class="size-4" />
|
||||
<XIcon class="size-3 shrink-0" />
|
||||
</template>
|
||||
</Sonner>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
|
||||
import { ref, watch, type Ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
export interface UseConfigPopoverOptions {
|
||||
/** Reactive open state from props */
|
||||
@@ -13,8 +11,6 @@ export interface UseConfigPopoverOptions {
|
||||
}
|
||||
|
||||
export function useConfigPopover(options: UseConfigPopoverOptions) {
|
||||
const { t } = useI18n()
|
||||
|
||||
const applying = ref(false)
|
||||
const loadingDevices = ref(false)
|
||||
|
||||
@@ -36,7 +32,6 @@ export function useConfigPopover(options: UseConfigPopoverOptions) {
|
||||
applying.value = true
|
||||
try {
|
||||
await applyFn()
|
||||
toast.success(t('config.applied'))
|
||||
} catch (e) {
|
||||
console.info('[ConfigPopover] Apply failed:', e)
|
||||
} finally {
|
||||
|
||||
@@ -43,12 +43,6 @@ export function useConsoleEvents(handlers: ConsoleEventHandlers) {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -56,10 +50,6 @@ export function useConsoleEvents(handlers: ConsoleEventHandlers) {
|
||||
if (systemStore.stream) {
|
||||
systemStore.stream.online = true
|
||||
}
|
||||
toast.success(t('console.deviceRecovered'), {
|
||||
description: t('console.deviceRecoveredDesc'),
|
||||
duration: 3000,
|
||||
})
|
||||
handlers.onStreamRecovered?.(_data)
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ export default {
|
||||
toggleLanguage: 'Toggle language',
|
||||
retry: 'Retry',
|
||||
},
|
||||
toast: {
|
||||
closeNotification: 'Dismiss notification',
|
||||
notificationsRegion: 'Notifications',
|
||||
},
|
||||
api: {
|
||||
operationFailed: 'Operation Failed',
|
||||
operationFailedDesc: 'Operation failed',
|
||||
|
||||
@@ -43,6 +43,10 @@ export default {
|
||||
toggleLanguage: '切换语言',
|
||||
retry: '重试',
|
||||
},
|
||||
toast: {
|
||||
closeNotification: '关闭通知',
|
||||
notificationsRegion: '通知',
|
||||
},
|
||||
api: {
|
||||
operationFailed: '操作失败',
|
||||
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()
|
||||
}
|
||||
|
||||
function handleStreamConfigChanging(data: any) {
|
||||
function handleStreamConfigChanging(_data: any) {
|
||||
if (retryTimeoutId !== null) {
|
||||
clearTimeout(retryTimeoutId)
|
||||
retryTimeoutId = null
|
||||
@@ -853,14 +853,9 @@ function handleStreamConfigChanging(data: any) {
|
||||
consecutiveErrors = 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
|
||||
|
||||
gracePeriodTimeoutId = window.setTimeout(() => {
|
||||
@@ -882,11 +877,6 @@ async function handleStreamConfigApplied(data: any) {
|
||||
}
|
||||
|
||||
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 }) {
|
||||
@@ -1158,11 +1148,6 @@ function handleStreamModeChanged(data: { mode: string; previous_mode: string })
|
||||
return
|
||||
}
|
||||
|
||||
toast.info(t('console.streamModeChanged'), {
|
||||
description: t('console.streamModeChangedDesc', { mode: data.mode.toUpperCase() }),
|
||||
duration: 5000,
|
||||
})
|
||||
|
||||
if (newMode !== videoMode.value) {
|
||||
syncToServerMode(newMode)
|
||||
}
|
||||
@@ -1238,11 +1223,6 @@ async function connectWebRTCOnly(codec: VideoMode = 'h264') {
|
||||
try {
|
||||
const success = await connectWebRTCSerial('connectWebRTCOnly')
|
||||
if (success) {
|
||||
toast.success(t('console.webrtcConnected'), {
|
||||
description: t('console.webrtcConnectedDesc'),
|
||||
duration: 3000,
|
||||
})
|
||||
|
||||
// Force video rebind even when the track already exists
|
||||
// This fixes missing video after returning to the page
|
||||
await rebindWebRTCVideo()
|
||||
@@ -1338,11 +1318,6 @@ async function switchToWebRTC(codec: VideoMode = 'h264') {
|
||||
success = await connectWebRTCSerial('switchToWebRTC')
|
||||
}
|
||||
if (success) {
|
||||
toast.success(t('console.webrtcConnected'), {
|
||||
description: t('console.webrtcConnectedDesc'),
|
||||
duration: 3000,
|
||||
})
|
||||
|
||||
await rebindWebRTCVideo()
|
||||
|
||||
videoLoading.value = false
|
||||
@@ -1570,7 +1545,6 @@ async function handleChangePassword() {
|
||||
changingPassword.value = true
|
||||
try {
|
||||
await authApi.changePassword(currentPassword.value, newPassword.value)
|
||||
toast.success(t('auth.passwordChanged'))
|
||||
|
||||
currentPassword.value = ''
|
||||
newPassword.value = ''
|
||||
@@ -1619,7 +1593,6 @@ async function handleReset() {
|
||||
async function handleWol(mac: string) {
|
||||
try {
|
||||
await atxConfigApi.sendWol(mac)
|
||||
toast.success(t('atx.wolSent'))
|
||||
} catch (e) {
|
||||
toast.error(t('atx.wolFailed'))
|
||||
}
|
||||
@@ -1684,10 +1657,6 @@ function handleKeyDown(e: KeyboardEvent) {
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -2039,10 +2008,6 @@ function handlePointerLockChange() {
|
||||
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 }
|
||||
|
||||
if (mouseMode.value === 'relative') {
|
||||
toast.info(t('console.relativeModeHint'), {
|
||||
description: t('console.relativeModeHintDesc'),
|
||||
duration: 5000,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user