Ariver
2026-05-18 8f11df460a247dfcaafd84ce3046605724d11998
VoiceSnapGo/frontend/src/components/settings/GeneralPage.svelte
@@ -2,7 +2,7 @@
  import { Call } from '@wailsio/runtime'
  import ToggleSwitch from '../shared/ToggleSwitch.svelte'
  import { t } from '../../lib/i18n'
  import { autoHide, soundFeedback, copyToClipboard, startAtLogin, hotkeyVK } from '../../lib/stores/config'
  import { autoHide, soundFeedback, copyToClipboard, startAtLogin, hotkeyVK, hotkeyMode } from '../../lib/stores/config'
  import { deviceName, engineStatus, engineHardwareInfo } from '../../lib/stores/app'
  import { hotkeyName } from '../../lib/stores/indicator'
@@ -21,6 +21,7 @@
  let status = $state('loading')
  let hwInfo = $state('')
  let currentKeyName = $state('Ctrl')
  let hotkeyModeVal: 'tap' | 'hold' = $state('hold')
  let isRecording = $state(false)
  let hintText = $state('')
  let devices = $state<InputDevice[]>([])
@@ -34,6 +35,7 @@
  const unsub6 = hotkeyName.subscribe(k => { currentKeyName = k })
  const unsub7 = soundFeedback.subscribe(v => { soundFeedbackVal = v })
  const unsub8 = copyToClipboard.subscribe(v => { copyToClipboardVal = v })
  const unsub9 = hotkeyMode.subscribe(v => { hotkeyModeVal = v })
  // Load actual device name from backend on mount
  async function loadDeviceName() {
@@ -69,6 +71,12 @@
      copyToClipboardVal = copy
    } catch {}
    try {
      const mode: any = await Call.ByName('voicesnap/services.ConfigService.GetHotkeyMode')
      const normalized = mode === 'tap' ? 'tap' : 'hold'
      hotkeyMode.set(normalized)
      hotkeyModeVal = normalized
    } catch {}
    try {
      const hideDock: any = await Call.ByName('voicesnap/services.ConfigService.GetHideDockIcon')
      hideDockIconVal = !!hideDock
    } catch {}
@@ -94,6 +102,14 @@
    copyToClipboard.set(checked)
    try {
      await Call.ByName('voicesnap/services.ConfigService.SetCopyToClipboard', checked)
    } catch {}
  }
  async function onHotkeyModeChange(mode: 'tap' | 'hold') {
    hotkeyMode.set(mode)
    hotkeyModeVal = mode
    try {
      await Call.ByName('voicesnap/services.ConfigService.SetHotkeyMode', mode)
    } catch {}
  }
@@ -212,7 +228,7 @@
  <!-- Header -->
  <div class="header">
    <h1 class="tagline">{t('home.tagline')}</h1>
    <p class="app-desc">{t('home.modeHint')}</p>
    <p class="app-desc">{hotkeyModeVal === 'tap' ? t('home.modeHintTap') : t('home.modeHintHold')}</p>
  </div>
  <!-- Hotkey -->
@@ -232,6 +248,31 @@
    {#if hintText}
      <p class="hint" class:recording={isRecording}>{hintText}</p>
    {/if}
    <div class="divider"></div>
    <div class="setting-row">
      <div class="setting-info">
        <span class="setting-label">{t('settings.hotkeyMode')}</span>
        <span class="setting-desc">
          {hotkeyModeVal === 'tap' ? t('settings.hotkeyModeTapDesc') : t('settings.hotkeyModeHoldDesc')}
        </span>
      </div>
      <div class="segmented" role="group" aria-label={t('settings.hotkeyMode')}>
        <button
          class:active={hotkeyModeVal === 'tap'}
          onclick={() => onHotkeyModeChange('tap')}
        >
          {t('settings.hotkeyModeTap')}
        </button>
        <button
          class:active={hotkeyModeVal === 'hold'}
          onclick={() => onHotkeyModeChange('hold')}
        >
          {t('settings.hotkeyModeHold')}
        </button>
      </div>
    </div>
  </div>
@@ -476,6 +517,35 @@
    color: var(--color-tertiary-label);
  }
  .segmented {
    display: inline-grid;
    grid-template-columns: 1fr 1fr;
    min-width: 148px;
    padding: 2px;
    border-radius: var(--radius-sm);
    background: var(--color-bg-secondary);
    flex-shrink: 0;
  }
  .segmented button {
    min-width: 66px;
    height: 30px;
    padding: 0 12px;
    border: none;
    border-radius: calc(var(--radius-sm) - 2px);
    background: transparent;
    color: var(--color-secondary-label);
    font-size: var(--font-size-sm);
    font-weight: 500;
    cursor: pointer;
    transition: background var(--transition-fast), color var(--transition-fast);
  }
  .segmented button.active {
    background: var(--color-blue);
    color: white;
  }
  .divider {
    height: 1px;
    background: var(--color-separator);