Files
SubMiner/src/config/definitions/options-core.ts
T
sudacode 4d1adebf11 refactor(hachidori): drop the Docker management upload for character dictionaries
Every supported host now takes dictionary ZIPs over the link, so remove
the port 8780 upload, its hachidori.externalHostManagementUrl override and
the hachidori config section. A linked host without linked-import-v1 now
reports the update or manual-import fix.
2026-10-05 09:50:52 -07:00

696 lines
25 KiB
TypeScript

import { ResolvedConfig } from '../../types/config';
import {
NOTIFICATION_TYPE_VALUES,
OVERLAY_NOTIFICATION_POSITION_VALUES,
SETTINGS_NOTIFICATION_TYPE_VALUES,
} from '../../types/notification';
import { ConfigOptionRegistryEntry } from './shared';
export function buildCoreConfigOptionRegistry(
defaultConfig: ResolvedConfig,
): ConfigOptionRegistryEntry[] {
const discreteBindings = [
{
id: 'toggleLookup',
defaultValue: defaultConfig.controller.bindings.toggleLookup,
description: 'Controller binding descriptor for toggling lookup.',
},
{
id: 'closeLookup',
defaultValue: defaultConfig.controller.bindings.closeLookup,
description: 'Controller binding descriptor for closing lookup.',
},
{
id: 'toggleKeyboardOnlyMode',
defaultValue: defaultConfig.controller.bindings.toggleKeyboardOnlyMode,
description: 'Controller binding descriptor for toggling keyboard-only mode.',
},
{
id: 'mineCard',
defaultValue: defaultConfig.controller.bindings.mineCard,
description: 'Controller binding descriptor for mining the active card.',
},
{
id: 'quitMpv',
defaultValue: defaultConfig.controller.bindings.quitMpv,
description: 'Controller binding descriptor for quitting mpv.',
},
{
id: 'previousAudio',
defaultValue: defaultConfig.controller.bindings.previousAudio,
description: 'Controller binding descriptor for previous Yomitan audio.',
},
{
id: 'nextAudio',
defaultValue: defaultConfig.controller.bindings.nextAudio,
description: 'Controller binding descriptor for next Yomitan audio.',
},
{
id: 'playCurrentAudio',
defaultValue: defaultConfig.controller.bindings.playCurrentAudio,
description: 'Controller binding descriptor for playing the current Yomitan audio.',
},
{
id: 'toggleMpvPause',
defaultValue: defaultConfig.controller.bindings.toggleMpvPause,
description: 'Controller binding descriptor for toggling mpv play/pause.',
},
] as const;
const axisBindings = [
{
id: 'leftStickHorizontal',
defaultValue: defaultConfig.controller.bindings.leftStickHorizontal,
description: 'Axis binding descriptor used for left/right token selection.',
},
{
id: 'leftStickVertical',
defaultValue: defaultConfig.controller.bindings.leftStickVertical,
description: 'Axis binding descriptor used for primary popup scrolling.',
},
{
id: 'rightStickHorizontal',
defaultValue: defaultConfig.controller.bindings.rightStickHorizontal,
description: 'Axis binding descriptor reserved for alternate right-stick mappings.',
},
{
id: 'rightStickVertical',
defaultValue: defaultConfig.controller.bindings.rightStickVertical,
description: 'Axis binding descriptor used for popup page jumps.',
},
] as const;
return [
{
path: 'dictionaryBackend',
kind: 'enum',
enumValues: ['yomitan', 'hachidori'],
defaultValue: defaultConfig.dictionaryBackend,
description: 'Dictionary lookup backend. Restart SubMiner after changing this setting.',
},
{
path: 'logging.level',
kind: 'enum',
enumValues: ['debug', 'info', 'warn', 'error'],
defaultValue: defaultConfig.logging.level,
description: 'Minimum log level for runtime logging.',
},
{
path: 'logging.rotation',
kind: 'number',
defaultValue: defaultConfig.logging.rotation,
description: 'Number of days of app, launcher, and mpv logs to retain.',
},
{
path: 'logging.files.app',
kind: 'boolean',
defaultValue: defaultConfig.logging.files.app,
description: 'Write SubMiner app runtime logs.',
},
{
path: 'logging.files.launcher',
kind: 'boolean',
defaultValue: defaultConfig.logging.files.launcher,
description: 'Write launcher command logs.',
},
{
path: 'logging.files.mpv',
kind: 'boolean',
defaultValue: defaultConfig.logging.files.mpv,
description: 'Write mpv player logs. Enable temporarily when debugging mpv/plugin startup.',
},
{
path: 'youtube.primarySubLanguages',
kind: 'string',
defaultValue: defaultConfig.youtube.primarySubLanguages.join(','),
description:
'Comma-separated primary subtitle language priority for managed subtitle auto-selection.',
},
{
path: 'youtube.subtitleSource',
kind: 'enum',
enumValues: ['youtube', 'whisper'],
enumLabels: {
youtube: 'Download YouTube captions',
whisper: 'Generate with Whisper',
},
defaultValue: defaultConfig.youtube.subtitleSource,
description:
'Where primary YouTube subtitles come from. Whisper transcribes the audio locally using the subtitleGeneration settings.',
},
{
path: 'youtube.mediaCache.mode',
kind: 'enum',
enumValues: ['direct', 'background'],
enumLabels: {
direct: 'Direct stream extraction',
background: 'Background media cache',
},
defaultValue: defaultConfig.youtube.mediaCache.mode,
description: 'How YouTube card audio/images are extracted.',
},
{
path: 'youtube.mediaCache.maxHeight',
kind: 'number',
defaultValue: defaultConfig.youtube.mediaCache.maxHeight,
description:
'Maximum video height downloaded for the YouTube background media cache. Set to 0 for unlimited.',
},
{
path: 'controller.enabled',
kind: 'boolean',
defaultValue: defaultConfig.controller.enabled,
description: 'Enable overlay controller support through the Chrome Gamepad API.',
},
{
path: 'controller.preferredGamepadId',
kind: 'string',
defaultValue: defaultConfig.controller.preferredGamepadId,
description: 'Preferred controller id saved from the controller config modal.',
},
{
path: 'controller.preferredGamepadLabel',
kind: 'string',
defaultValue: defaultConfig.controller.preferredGamepadLabel,
description: 'Preferred controller display label saved for diagnostics.',
},
{
path: 'controller.smoothScroll',
kind: 'boolean',
defaultValue: defaultConfig.controller.smoothScroll,
description: 'Use smooth scrolling for controller-driven popup scroll input.',
},
{
path: 'controller.scrollPixelsPerSecond',
kind: 'number',
defaultValue: defaultConfig.controller.scrollPixelsPerSecond,
description: 'Base popup scroll speed for controller stick input.',
},
{
path: 'controller.horizontalJumpPixels',
kind: 'number',
defaultValue: defaultConfig.controller.horizontalJumpPixels,
description: 'Popup page-jump distance for controller jump input.',
},
{
path: 'controller.stickDeadzone',
kind: 'number',
defaultValue: defaultConfig.controller.stickDeadzone,
description: 'Deadzone applied to controller stick axes.',
},
{
path: 'controller.triggerInputMode',
kind: 'enum',
enumValues: ['auto', 'digital', 'analog'],
defaultValue: defaultConfig.controller.triggerInputMode,
description:
'How controller triggers are interpreted: auto, pressed-only, or thresholded analog.',
},
{
path: 'controller.triggerDeadzone',
kind: 'number',
defaultValue: defaultConfig.controller.triggerDeadzone,
description:
'Minimum analog trigger value required when trigger input uses auto or analog mode.',
},
{
path: 'controller.repeatDelayMs',
kind: 'number',
defaultValue: defaultConfig.controller.repeatDelayMs,
description: 'Delay before repeating held controller actions.',
},
{
path: 'controller.repeatIntervalMs',
kind: 'number',
defaultValue: defaultConfig.controller.repeatIntervalMs,
description: 'Repeat interval for held controller actions.',
},
{
path: 'controller.buttonIndices',
kind: 'object',
defaultValue: defaultConfig.controller.buttonIndices,
description:
'Semantic button-name reference mapping used for debug output. Updating it does not rewrite existing raw binding descriptors.',
},
{
path: 'controller.buttonIndices.select',
kind: 'number',
defaultValue: defaultConfig.controller.buttonIndices.select,
description: 'Raw button index used for the controller select/minus/back button.',
},
{
path: 'controller.buttonIndices.buttonSouth',
kind: 'number',
defaultValue: defaultConfig.controller.buttonIndices.buttonSouth,
description: 'Raw button index used for controller south/A button input.',
},
{
path: 'controller.buttonIndices.buttonEast',
kind: 'number',
defaultValue: defaultConfig.controller.buttonIndices.buttonEast,
description: 'Raw button index used for controller east/B button input.',
},
{
path: 'controller.buttonIndices.buttonNorth',
kind: 'number',
defaultValue: defaultConfig.controller.buttonIndices.buttonNorth,
description: 'Raw button index used for controller north/Y button input.',
},
{
path: 'controller.buttonIndices.buttonWest',
kind: 'number',
defaultValue: defaultConfig.controller.buttonIndices.buttonWest,
description: 'Raw button index used for controller west/X button input.',
},
{
path: 'controller.buttonIndices.leftShoulder',
kind: 'number',
defaultValue: defaultConfig.controller.buttonIndices.leftShoulder,
description: 'Raw button index used for controller left shoulder input.',
},
{
path: 'controller.buttonIndices.rightShoulder',
kind: 'number',
defaultValue: defaultConfig.controller.buttonIndices.rightShoulder,
description: 'Raw button index used for controller right shoulder input.',
},
{
path: 'controller.buttonIndices.leftStickPress',
kind: 'number',
defaultValue: defaultConfig.controller.buttonIndices.leftStickPress,
description: 'Raw button index used for controller L3 input.',
},
{
path: 'controller.buttonIndices.rightStickPress',
kind: 'number',
defaultValue: defaultConfig.controller.buttonIndices.rightStickPress,
description: 'Raw button index used for controller R3 input.',
},
{
path: 'controller.buttonIndices.leftTrigger',
kind: 'number',
defaultValue: defaultConfig.controller.buttonIndices.leftTrigger,
description: 'Raw button index used for controller L2 input.',
},
{
path: 'controller.buttonIndices.rightTrigger',
kind: 'number',
defaultValue: defaultConfig.controller.buttonIndices.rightTrigger,
description: 'Raw button index used for controller R2 input.',
},
{
path: 'controller.bindings',
kind: 'object',
defaultValue: defaultConfig.controller.bindings,
description:
'Raw controller binding descriptors saved by Alt+C learn mode. For discrete axis bindings, kind "axis" requires axisIndex and direction.',
},
{
path: 'controller.profiles',
kind: 'object',
defaultValue: defaultConfig.controller.profiles,
description:
'Per-controller binding and button-index overrides keyed by the controller id reported by the Gamepad API.',
},
...discreteBindings.flatMap((binding) => [
{
path: `controller.bindings.${binding.id}`,
kind: 'object' as const,
defaultValue: binding.defaultValue,
description: `${binding.description} Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.`,
},
{
path: `controller.bindings.${binding.id}.kind`,
kind: 'enum' as const,
enumValues: ['none', 'button', 'axis'],
defaultValue: binding.defaultValue.kind,
description:
'Discrete binding input source kind. When kind is "axis", set both axisIndex and direction.',
},
{
path: `controller.bindings.${binding.id}.buttonIndex`,
kind: 'number' as const,
defaultValue:
binding.defaultValue.kind === 'button' ? binding.defaultValue.buttonIndex : undefined,
description: 'Raw button index captured for this discrete controller action.',
},
{
path: `controller.bindings.${binding.id}.axisIndex`,
kind: 'number' as const,
defaultValue:
binding.defaultValue.kind === 'axis' ? binding.defaultValue.axisIndex : undefined,
description: 'Raw axis index captured for this discrete controller action.',
},
{
path: `controller.bindings.${binding.id}.direction`,
kind: 'enum' as const,
enumValues: ['negative', 'positive'],
defaultValue:
binding.defaultValue.kind === 'axis' ? binding.defaultValue.direction : undefined,
description:
'Axis direction captured for this discrete controller action. Required when kind is "axis".',
},
]),
...axisBindings.flatMap((binding) => [
{
path: `controller.bindings.${binding.id}`,
kind: 'object' as const,
defaultValue: binding.defaultValue,
description: `${binding.description} Use Alt+C learn mode or set a raw axis descriptor manually.`,
},
{
path: `controller.bindings.${binding.id}.kind`,
kind: 'enum' as const,
enumValues: ['none', 'axis'],
defaultValue: binding.defaultValue.kind,
description: 'Analog binding input source kind.',
},
{
path: `controller.bindings.${binding.id}.axisIndex`,
kind: 'number' as const,
defaultValue:
binding.defaultValue.kind === 'axis' ? binding.defaultValue.axisIndex : undefined,
description: 'Raw axis index captured for this analog controller action.',
},
{
path: `controller.bindings.${binding.id}.dpadFallback`,
kind: 'enum' as const,
enumValues: ['none', 'horizontal', 'vertical'],
defaultValue:
binding.defaultValue.kind === 'axis' ? binding.defaultValue.dpadFallback : undefined,
description:
'Optional D-pad fallback used when this analog controller action should also read D-pad input.',
},
]),
{
path: 'texthooker.launchAtStartup',
kind: 'boolean',
defaultValue: defaultConfig.texthooker.launchAtStartup,
description: 'Launch texthooker server automatically when SubMiner starts.',
},
{
path: 'texthooker.openBrowser',
kind: 'boolean',
defaultValue: defaultConfig.texthooker.openBrowser,
description: 'Open the texthooker page in the default browser when the server starts.',
},
{
path: 'subtitlePosition.yPercent',
kind: 'number',
defaultValue: defaultConfig.subtitlePosition.yPercent,
description:
'Vertical position of the subtitle overlay expressed as a percentage from the bottom of the screen.',
},
{
path: 'auto_start_overlay',
kind: 'boolean',
defaultValue: defaultConfig.auto_start_overlay,
description:
'Show the visible subtitle overlay automatically when the bundled mpv plugin starts SubMiner.',
},
{
path: 'secondarySub.secondarySubLanguages',
kind: 'array',
defaultValue: defaultConfig.secondarySub.secondarySubLanguages,
description:
'Language code priority list used to auto-select a secondary subtitle track when available.',
},
{
path: 'secondarySub.autoLoadSecondarySub',
kind: 'boolean',
defaultValue: defaultConfig.secondarySub.autoLoadSecondarySub,
description:
'Automatically load a matching secondary subtitle when the primary subtitle loads.',
},
{
path: 'secondarySub.defaultMode',
kind: 'enum',
enumValues: ['hidden', 'visible', 'hover'],
defaultValue: defaultConfig.secondarySub.defaultMode,
description: 'Default visibility mode for the secondary subtitle bar.',
},
{
path: 'websocket.enabled',
kind: 'enum',
enumValues: ['auto', 'true', 'false'],
defaultValue: defaultConfig.websocket.enabled,
description: 'Built-in subtitle websocket server mode.',
},
{
path: 'websocket.port',
kind: 'number',
defaultValue: defaultConfig.websocket.port,
description: 'Built-in subtitle websocket server port.',
},
{
path: 'annotationWebsocket.enabled',
kind: 'boolean',
defaultValue: defaultConfig.annotationWebsocket.enabled,
description: 'Annotated subtitle websocket server enabled state.',
},
{
path: 'annotationWebsocket.port',
kind: 'number',
defaultValue: defaultConfig.annotationWebsocket.port,
description: 'Annotated subtitle websocket server port.',
},
{
path: 'subsync.replace',
kind: 'boolean',
defaultValue: defaultConfig.subsync.replace,
description: 'Replace the active subtitle file when sync completes.',
},
{
path: 'subsync.alass_path',
kind: 'string',
defaultValue: defaultConfig.subsync.alass_path,
description:
'Optional absolute path to the alass binary used by subsync. Leave empty to auto-discover from PATH.',
},
{
path: 'subsync.ffsubsync_path',
kind: 'string',
defaultValue: defaultConfig.subsync.ffsubsync_path,
description:
'Optional absolute path to the ffsubsync binary used by subsync. Leave empty to auto-discover from PATH.',
},
{
path: 'subsync.ffmpeg_path',
kind: 'string',
defaultValue: defaultConfig.subsync.ffmpeg_path,
description:
'Optional absolute path to the ffmpeg binary used by subsync. Leave empty to auto-discover from PATH.',
},
{
path: 'startupWarmups.lowPowerMode',
kind: 'boolean',
defaultValue: defaultConfig.startupWarmups.lowPowerMode,
description: 'Defer startup warmups except Yomitan extension.',
},
{
path: 'startupWarmups.mecab',
kind: 'boolean',
defaultValue: defaultConfig.startupWarmups.mecab,
description: 'Warm up MeCab tokenizer at startup.',
},
{
path: 'startupWarmups.yomitanExtension',
kind: 'boolean',
defaultValue: defaultConfig.startupWarmups.yomitanExtension,
description: 'Warm up Yomitan extension at startup.',
},
{
path: 'startupWarmups.subtitleDictionaries',
kind: 'boolean',
defaultValue: defaultConfig.startupWarmups.subtitleDictionaries,
description: 'Warm up subtitle dictionaries at startup.',
},
{
path: 'startupWarmups.jellyfinRemoteSession',
kind: 'boolean',
defaultValue: defaultConfig.startupWarmups.jellyfinRemoteSession,
description: 'Warm up Jellyfin remote session at startup.',
},
{
path: 'updates.enabled',
kind: 'boolean',
defaultValue: defaultConfig.updates.enabled,
description: 'Run automatic update checks in the background.',
},
{
path: 'updates.checkIntervalHours',
kind: 'number',
defaultValue: defaultConfig.updates.checkIntervalHours,
description: 'Minimum hours between automatic update checks.',
},
{
path: 'updates.notificationType',
kind: 'enum',
enumValues: NOTIFICATION_TYPE_VALUES,
settingsEnumValues: SETTINGS_NOTIFICATION_TYPE_VALUES,
defaultValue: defaultConfig.updates.notificationType,
description:
'How SubMiner announces available updates. overlay shows notifications on the overlay, system uses OS notifications, both uses overlay and system. osd and osd-system are legacy config-file-only values.',
},
{
path: 'updates.channel',
kind: 'enum',
enumValues: ['stable', 'prerelease'],
defaultValue: defaultConfig.updates.channel,
description: 'Release channel used for update checks.',
},
{
path: 'notifications.overlayPosition',
kind: 'enum',
enumValues: OVERLAY_NOTIFICATION_POSITION_VALUES,
defaultValue: defaultConfig.notifications.overlayPosition,
description: 'Position for in-overlay notification cards.',
},
{
path: 'shortcuts.multiCopyTimeoutMs',
kind: 'number',
defaultValue: defaultConfig.shortcuts.multiCopyTimeoutMs,
description: 'Timeout for multi-copy/mine modes.',
},
{
path: 'shortcuts.toggleVisibleOverlayGlobal',
kind: 'string',
defaultValue: defaultConfig.shortcuts.toggleVisibleOverlayGlobal,
description:
'Global accelerator that toggles overlay visibility from anywhere on the system. Use null to disable.',
},
{
path: 'shortcuts.copySubtitle',
kind: 'string',
defaultValue: defaultConfig.shortcuts.copySubtitle,
description: 'Accelerator that copies the current subtitle line to the clipboard.',
},
{
path: 'shortcuts.copySubtitleMultiple',
kind: 'string',
defaultValue: defaultConfig.shortcuts.copySubtitleMultiple,
description:
'Accelerator that copies consecutive subtitle lines while the multi-copy window stays open.',
},
{
path: 'shortcuts.updateLastCardFromClipboard',
kind: 'string',
defaultValue: defaultConfig.shortcuts.updateLastCardFromClipboard,
description:
'Accelerator that updates the last mined Anki card using the current clipboard contents.',
},
{
path: 'shortcuts.triggerFieldGrouping',
kind: 'string',
defaultValue: defaultConfig.shortcuts.triggerFieldGrouping,
description: 'Accelerator that triggers Kiku field grouping on duplicate cards.',
},
{
path: 'shortcuts.triggerSubsync',
kind: 'string',
defaultValue: defaultConfig.shortcuts.triggerSubsync,
description: 'Accelerator that triggers subsync against the active subtitle file.',
},
{
path: 'shortcuts.mineSentence',
kind: 'string',
defaultValue: defaultConfig.shortcuts.mineSentence,
description: 'Accelerator that mines the current sentence as a new Anki card.',
},
{
path: 'shortcuts.mineSentenceMultiple',
kind: 'string',
defaultValue: defaultConfig.shortcuts.mineSentenceMultiple,
description:
'Accelerator that mines consecutive sentences while the multi-mine window stays open.',
},
{
path: 'shortcuts.toggleSecondarySub',
kind: 'string',
defaultValue: defaultConfig.shortcuts.toggleSecondarySub,
description: 'Accelerator that toggles the secondary subtitle bar visibility.',
},
{
path: 'shortcuts.markAudioCard',
kind: 'string',
defaultValue: defaultConfig.shortcuts.markAudioCard,
description: 'Accelerator that marks the last mined card as an audio card.',
},
{
path: 'shortcuts.openCharacterDictionaryManager',
kind: 'string',
defaultValue: defaultConfig.shortcuts.openCharacterDictionaryManager,
description: 'Accelerator that opens the character dictionary manager modal.',
},
{
path: 'shortcuts.openRuntimeOptions',
kind: 'string',
defaultValue: defaultConfig.shortcuts.openRuntimeOptions,
description: 'Accelerator that opens the runtime options modal.',
},
{
path: 'shortcuts.openJimaku',
kind: 'string',
defaultValue: defaultConfig.shortcuts.openJimaku,
description: 'Accelerator that opens the Jimaku subtitle search modal.',
},
{
path: 'shortcuts.openTsukihime',
kind: 'string',
defaultValue: defaultConfig.shortcuts.openTsukihime,
description:
'Accelerator that opens the TsukiHime subtitle search modal (configured secondary/Japanese primary tabs).',
},
{
path: 'shortcuts.openSessionHelp',
kind: 'string',
defaultValue: defaultConfig.shortcuts.openSessionHelp,
description: 'Accelerator that opens the session help / keybinding cheatsheet.',
},
{
path: 'shortcuts.openSubtitleSelection',
kind: 'string',
defaultValue: defaultConfig.shortcuts.openSubtitleSelection,
description:
'Open subtitle selection when enabled. Use g-s to press g then s. Set null to unbind.',
},
{
path: 'shortcuts.openSubtitleGeneration',
kind: 'string',
defaultValue: defaultConfig.shortcuts.openSubtitleGeneration,
description: 'Accelerator that opens the standalone Japanese subtitle generation modal.',
},
{
path: 'shortcuts.openControllerSelect',
kind: 'string',
defaultValue: defaultConfig.shortcuts.openControllerSelect,
description: 'Accelerator that opens the controller selection and learn-mode modal.',
},
{
path: 'shortcuts.openControllerDebug',
kind: 'string',
defaultValue: defaultConfig.shortcuts.openControllerDebug,
description:
'Accelerator that opens the controller debug modal with live axis/button readouts.',
},
{
path: 'shortcuts.toggleSubtitleSidebar',
kind: 'string',
defaultValue: defaultConfig.shortcuts.toggleSubtitleSidebar,
description: 'Accelerator that toggles the subtitle sidebar visibility.',
},
{
path: 'shortcuts.toggleNotificationHistory',
kind: 'string',
defaultValue: defaultConfig.shortcuts.toggleNotificationHistory,
description: 'Accelerator that toggles the overlay notification history panel.',
},
{
path: 'shortcuts.appendClipboardVideoToQueue',
kind: 'string',
defaultValue: defaultConfig.shortcuts.appendClipboardVideoToQueue,
description: 'Accelerator that appends a video path from the clipboard to the mpv playlist.',
},
];
}