Ariver
2026-07-12 24f551a39eae849d891da26cc91f90d354e3a2db
C1.source/privatevoice.src/frontend/src/components/settings/HotkeysPage.svelte
@@ -1,16 +1,24 @@
<script lang="ts">
  import { onDestroy } from 'svelte'
  import { Call } from '@wailsio/runtime'
  import { t } from '../../lib/i18n'
  import { hotkeyVK } from '../../lib/stores/config'
  import { hotkeyName } from '../../lib/stores/indicator'
  let currentKeyName = $state('Ctrl')
  let currentKeyName = $state('')
  let isRecording = $state(false)
  let hintText = $state('')
  let activeKeyHandler: ((e: KeyboardEvent) => void) | null = null
  const unsub = hotkeyName.subscribe(k => { currentKeyName = k })
  onDestroy(() => {
    unsub()
    clearHotkeyCapture()
  })
  async function startRecordingHotkey() {
    clearHotkeyCapture()
    isRecording = true
    hintText = t('hotkeys.pressAnyKey')
@@ -23,6 +31,12 @@
      e.preventDefault()
      e.stopPropagation()
      if (isHotkeyCaptureCancel(e)) {
        hintText = t('status.cancelled')
        clearHotkeyCapture()
        return
      }
      const vk = mapKeyToVK(e)
      if (vk > 0) {
        isRecording = false
@@ -34,7 +48,7 @@
          try {
            await Call.ByName('voicesnap/services.HotkeyService.SetHotkey', vk)
            const name: any = await Call.ByName('voicesnap/services.HotkeyService.GetKeyName', vk)
            if (name) {
            if (typeof name === 'string') {
              hotkeyName.set(name)
              currentKeyName = name
            }
@@ -44,11 +58,27 @@
          } catch {}
        })()
        document.removeEventListener('keydown', onKey)
        clearHotkeyCapture()
      }
    }
    activeKeyHandler = onKey
    document.addEventListener('keydown', onKey)
  }
  function isHotkeyCaptureCancel(e: KeyboardEvent): boolean {
    return e.key === 'Escape' || e.key === 'Esc'
  }
  function clearHotkeyCapture() {
    if (activeKeyHandler) {
      document.removeEventListener('keydown', activeKeyHandler)
      activeKeyHandler = null
    }
    if (isRecording) {
      isRecording = false
      void Call.ByName('voicesnap/services.HotkeyService.StopRecordingHotkey').catch(() => {})
    }
  }
  function mapKeyToVK(e: KeyboardEvent): number {
@@ -85,14 +115,18 @@
    <div class="hotkey-display">
      <div class="hotkey-info">
        <span class="hotkey-label">{t('hotkeys.current')}</span>
        <div class="hotkey-key" class:recording={isRecording}>
          {isRecording ? '...' : currentKeyName}
        <div class="hotkey-key" class:recording={isRecording} class:unset={!isRecording && !currentKeyName}>
          {isRecording ? '...' : (currentKeyName || t('hotkeys.unset'))}
        </div>
      </div>
      <button class="change-btn" onclick={startRecordingHotkey} disabled={isRecording}>
        {t('hotkeys.change')}
        {currentKeyName ? t('hotkeys.change') : t('hotkeys.set')}
      </button>
    </div>
    {#if !currentKeyName && !isRecording}
      <p class="hint">{t('hotkeys.unsetDesc')}</p>
    {/if}
    {#if hintText}
      <p class="hint" class:recording={isRecording}>{hintText}</p>
@@ -173,6 +207,12 @@
    animation: pulse 1s infinite;
  }
  .hotkey-key.unset {
    color: var(--color-secondary-label);
    font-size: var(--font-size-base);
    font-weight: 500;
  }
  @keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }