feat(preferences): enhance selection management with type safety and refactor preferences structure

- Introduced new types for selection management: `SelectionActionItem`, `SelectionFilterMode`, and `SelectionTriggerMode` to improve type safety across the application.
- Updated `PreferencesType` to utilize these new types, ensuring better consistency and clarity in preference definitions.
- Refactored `SelectionService` and related components to adopt the new types, enhancing the handling of selection actions and preferences.
- Cleaned up deprecated types and improved the overall structure of preference management, aligning with recent updates in the preference system.
This commit is contained in:
fullex 2025-08-16 08:57:48 +08:00
parent 087e825086
commit b15778b16b
11 changed files with 80 additions and 123 deletions

View File

@ -1,3 +1,5 @@
import type { SelectionActionItem, SelectionFilterMode, SelectionTriggerMode } from './types'
/** /**
* Auto-generated preferences configuration * Auto-generated preferences configuration
* Generated at: 2025-08-15T03:23:46.568Z * Generated at: 2025-08-15T03:23:46.568Z
@ -13,7 +15,6 @@
"interfaces": { "order": "alphabetically" }, "interfaces": { "order": "alphabetically" },
"typeLiterals": { "order": "alphabetically" } "typeLiterals": { "order": "alphabetically" }
}] */ }] */
export interface PreferencesType { export interface PreferencesType {
default: { default: {
// redux/settings/enableDeveloperMode // redux/settings/enableDeveloperMode
@ -289,7 +290,7 @@ export interface PreferencesType {
// redux/settings/readClipboardAtStartup // redux/settings/readClipboardAtStartup
'feature.quick_assistant.read_clipboard_at_startup': boolean 'feature.quick_assistant.read_clipboard_at_startup': boolean
// redux/selectionStore/actionItems // redux/selectionStore/actionItems
'feature.selection.action_items': unknown[] 'feature.selection.action_items': SelectionActionItem[]
// redux/selectionStore/actionWindowOpacity // redux/selectionStore/actionWindowOpacity
'feature.selection.action_window_opacity': number 'feature.selection.action_window_opacity': number
// redux/selectionStore/isAutoClose // redux/selectionStore/isAutoClose
@ -303,13 +304,13 @@ export interface PreferencesType {
// redux/selectionStore/filterList // redux/selectionStore/filterList
'feature.selection.filter_list': string[] 'feature.selection.filter_list': string[]
// redux/selectionStore/filterMode // redux/selectionStore/filterMode
'feature.selection.filter_mode': string 'feature.selection.filter_mode': SelectionFilterMode
// redux/selectionStore/isFollowToolbar // redux/selectionStore/isFollowToolbar
'feature.selection.follow_toolbar': boolean 'feature.selection.follow_toolbar': boolean
// redux/selectionStore/isRemeberWinSize // redux/selectionStore/isRemeberWinSize
'feature.selection.remember_win_size': boolean 'feature.selection.remember_win_size': boolean
// redux/selectionStore/triggerMode // redux/selectionStore/triggerMode
'feature.selection.trigger_mode': string 'feature.selection.trigger_mode': SelectionTriggerMode
// redux/shortcuts/shortcuts.clear_topic // redux/shortcuts/shortcuts.clear_topic
'shortcut.chat.clear': Record<string, unknown> 'shortcut.chat.clear': Record<string, unknown>
// redux/shortcuts/shortcuts.copy_last_message // redux/shortcuts/shortcuts.copy_last_message

View File

@ -3,6 +3,10 @@ import { PreferencesType } from './preferences'
export type PreferenceDefaultScopeType = PreferencesType['default'] export type PreferenceDefaultScopeType = PreferencesType['default']
export type PreferenceKeyType = keyof PreferenceDefaultScopeType export type PreferenceKeyType = keyof PreferenceDefaultScopeType
export interface PreferenceUpdateOptions {
optimistic: boolean
}
export type PreferenceShortcutType = { export type PreferenceShortcutType = {
key: string[] key: string[]
editable: boolean editable: boolean
@ -10,6 +14,16 @@ export type PreferenceShortcutType = {
system: boolean system: boolean
} }
export interface PreferenceUpdateOptions { export type SelectionTriggerMode = 'selected' | 'ctrlkey' | 'shortcut'
optimistic: boolean export type SelectionFilterMode = 'default' | 'whitelist' | 'blacklist'
export interface SelectionActionItem {
id: string
name: string
enabled: boolean
isBuiltIn: boolean
icon?: string
prompt?: string
assistantId?: string
selectedText?: string
searchEngine?: string
} }

View File

@ -159,16 +159,16 @@ if (!app.requestSingleInstanceLock()) {
await preferenceService.initialize() await preferenceService.initialize()
// Create two test windows for cross-window preference sync testing // // Create two test windows for cross-window preference sync testing
logger.info('Creating test windows for PreferenceService cross-window sync testing') // logger.info('Creating test windows for PreferenceService cross-window sync testing')
const testWindow1 = dataRefactorMigrateService.createTestWindow() // const testWindow1 = dataRefactorMigrateService.createTestWindow()
const testWindow2 = dataRefactorMigrateService.createTestWindow() // const testWindow2 = dataRefactorMigrateService.createTestWindow()
// Position windows to avoid overlap // // Position windows to avoid overlap
testWindow1.once('ready-to-show', () => { // testWindow1.once('ready-to-show', () => {
const [x, y] = testWindow1.getPosition() // const [x, y] = testWindow1.getPosition()
testWindow2.setPosition(x + 50, y + 50) // testWindow2.setPosition(x + 50, y + 50)
}) // })
/************FOR TESTING ONLY END****************/ /************FOR TESTING ONLY END****************/
// Set app user model id for windows // Set app user model id for windows

View File

@ -2,6 +2,7 @@ import { preferenceService } from '@data/PreferenceService'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { SELECTION_FINETUNED_LIST, SELECTION_PREDEFINED_BLACKLIST } from '@main/configs/SelectionConfig' import { SELECTION_FINETUNED_LIST, SELECTION_PREDEFINED_BLACKLIST } from '@main/configs/SelectionConfig'
import { isDev, isMac, isWin } from '@main/constant' import { isDev, isMac, isWin } from '@main/constant'
import type { SelectionActionItem, SelectionFilterMode, SelectionTriggerMode } from '@shared/data/types'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { app, BrowserWindow, ipcMain, screen, systemPreferences } from 'electron' import { app, BrowserWindow, ipcMain, screen, systemPreferences } from 'electron'
import { join } from 'path' import { join } from 'path'
@ -13,8 +14,6 @@ import type {
TextSelectionData TextSelectionData
} from 'selection-hook' } from 'selection-hook'
import type { ActionItem } from '../../renderer/src/types/selectionTypes'
const logger = loggerService.withContext('SelectionService') const logger = loggerService.withContext('SelectionService')
const isSupportedOS = isWin || isMac const isSupportedOS = isWin || isMac
@ -1248,7 +1247,7 @@ export class SelectionService {
* @param actionItem Action item to process * @param actionItem Action item to process
* @param isFullScreen [macOS] only macOS has the available isFullscreen mode * @param isFullScreen [macOS] only macOS has the available isFullscreen mode
*/ */
public processAction(actionItem: ActionItem, isFullScreen: boolean = false): void { public processAction(actionItem: SelectionActionItem, isFullScreen: boolean = false): void {
const actionWindow = this.popActionWindow() const actionWindow = this.popActionWindow()
actionWindow.webContents.send(IpcChannel.Selection_UpdateActionData, actionItem) actionWindow.webContents.send(IpcChannel.Selection_UpdateActionData, actionItem)
@ -1476,7 +1475,7 @@ export class SelectionService {
preferenceService.set('feature.selection.enabled', enabled) preferenceService.set('feature.selection.enabled', enabled)
}) })
ipcMain.handle(IpcChannel.Selection_SetTriggerMode, (_, triggerMode: string) => { ipcMain.handle(IpcChannel.Selection_SetTriggerMode, (_, triggerMode: SelectionTriggerMode) => {
preferenceService.set('feature.selection.trigger_mode', triggerMode) preferenceService.set('feature.selection.trigger_mode', triggerMode)
}) })
@ -1488,7 +1487,7 @@ export class SelectionService {
preferenceService.set('feature.selection.remember_win_size', isRemeberWinSize) preferenceService.set('feature.selection.remember_win_size', isRemeberWinSize)
}) })
ipcMain.handle(IpcChannel.Selection_SetFilterMode, (_, filterMode: string) => { ipcMain.handle(IpcChannel.Selection_SetFilterMode, (_, filterMode: SelectionFilterMode) => {
preferenceService.set('feature.selection.filter_mode', filterMode) preferenceService.set('feature.selection.filter_mode', filterMode)
}) })
@ -1497,9 +1496,12 @@ export class SelectionService {
}) })
// [macOS] only macOS has the available isFullscreen mode // [macOS] only macOS has the available isFullscreen mode
ipcMain.handle(IpcChannel.Selection_ProcessAction, (_, actionItem: ActionItem, isFullScreen: boolean = false) => { ipcMain.handle(
selectionService?.processAction(actionItem, isFullScreen) IpcChannel.Selection_ProcessAction,
}) (_, actionItem: SelectionActionItem, isFullScreen: boolean = false) => {
selectionService?.processAction(actionItem, isFullScreen)
}
)
ipcMain.handle(IpcChannel.Selection_ActionWindowClose, (event) => { ipcMain.handle(IpcChannel.Selection_ActionWindowClose, (event) => {
const actionWindow = BrowserWindow.fromWebContents(event.sender) const actionWindow = BrowserWindow.fromWebContents(event.sender)

View File

@ -1,5 +1,6 @@
import '@renderer/databases' import '@renderer/databases'
import { preferenceService } from '@data/PreferenceService'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import store, { persistor } from '@renderer/store' import store, { persistor } from '@renderer/store'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
@ -15,6 +16,8 @@ import Router from './Router'
const logger = loggerService.withContext('App.tsx') const logger = loggerService.withContext('App.tsx')
preferenceService.loadAll()
function App(): React.ReactElement { function App(): React.ReactElement {
logger.info('App initialized') logger.info('App initialized')

View File

@ -109,7 +109,7 @@ export class PreferenceService {
this.notifyChangeListeners(key) this.notifyChangeListeners(key)
// Auto-subscribe to this key for future updates // Auto-subscribe to this key for future updates
await this.subscribeToKeyInternal(key) await this.subscribeToKeyInternal([key])
return value return value
} catch (error) { } catch (error) {
@ -230,7 +230,7 @@ export class PreferenceService {
this.notifyChangeListeners(key) this.notifyChangeListeners(key)
await this.subscribeToKeyInternal(key as PreferenceKeyType) await this.subscribeToKeyInternal([key as PreferenceKeyType])
} }
return { ...cachedResults, ...uncachedResults } return { ...cachedResults, ...uncachedResults }
@ -343,15 +343,16 @@ export class PreferenceService {
/** /**
* Subscribe to a specific key for change notifications * Subscribe to a specific key for change notifications
*/ */
private async subscribeToKeyInternal(key: PreferenceKeyType): Promise<void> { private async subscribeToKeyInternal(keys: PreferenceKeyType[]): Promise<void> {
if (this.subscribedKeys.has(key)) return const keysToSubscribe = keys.filter((key) => !this.subscribedKeys.has(key))
if (keysToSubscribe.length === 0) return
try { try {
await window.api.preference.subscribe([key]) await window.api.preference.subscribe(keysToSubscribe)
this.subscribedKeys.add(key) keysToSubscribe.forEach((key) => this.subscribedKeys.add(key))
logger.debug(`Subscribed to preference key: ${key}`) logger.debug(`Subscribed to preference keys: ${keysToSubscribe.join(', ')}`)
} catch (error) { } catch (error) {
logger.error(`Failed to subscribe to preference key ${key}:`, error as Error) logger.error(`Failed to subscribe to preference keys ${keysToSubscribe.join(', ')}:`, error as Error)
} }
} }
@ -379,7 +380,7 @@ export class PreferenceService {
keyListeners.add(callback) keyListeners.add(callback)
// Auto-subscribe to this key for updates // Auto-subscribe to this key for updates
this.subscribeToKeyInternal(key) this.subscribeToKeyInternal([key])
return () => { return () => {
keyListeners.delete(callback) keyListeners.delete(callback)
@ -417,11 +418,10 @@ export class PreferenceService {
// Notify change listeners for the loaded value // Notify change listeners for the loaded value
this.notifyChangeListeners(key) this.notifyChangeListeners(key)
// Auto-subscribe to this key if not already subscribed
await this.subscribeToKeyInternal(key as PreferenceKeyType)
} }
await this.subscribeToKeyInternal(Object.keys(allPreferences) as PreferenceKeyType[])
this.fullCacheLoaded = true this.fullCacheLoaded = true
logger.info(`Loaded all ${Object.keys(allPreferences).length} preferences into cache`) logger.info(`Loaded all ${Object.keys(allPreferences).length} preferences into cache`)

View File

@ -1,11 +1,9 @@
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import { isMac, isWin } from '@renderer/config/constant' import { isMac, isWin } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { getSelectionDescriptionLabel } from '@renderer/i18n/label' import { getSelectionDescriptionLabel } from '@renderer/i18n/label'
import { FilterMode, TriggerMode } from '@renderer/types/selectionTypes'
import { ActionItem } from '@renderer/types/selectionTypes'
import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar' import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar'
import type { SelectionFilterMode, SelectionTriggerMode } from '@shared/data/types'
import { Button, Radio, Row, Slider, Switch, Tooltip } from 'antd' import { Button, Radio, Row, Slider, Switch, Tooltip } from 'antd'
import { CircleHelp, Edit2 } from 'lucide-react' import { CircleHelp, Edit2 } from 'lucide-react'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
@ -26,35 +24,9 @@ import MacProcessTrustHintModal from './components/MacProcessTrustHintModal'
import SelectionActionsList from './components/SelectionActionsList' import SelectionActionsList from './components/SelectionActionsList'
import SelectionFilterListModal from './components/SelectionFilterListModal' import SelectionFilterListModal from './components/SelectionFilterListModal'
const logger = loggerService.withContext('Settings:SelectionAssistant')
const SelectionAssistantSettings: FC = () => { const SelectionAssistantSettings: FC = () => {
const { theme } = useTheme() const { theme } = useTheme()
const { t } = useTranslation() const { t } = useTranslation()
// const {
// selectionEnabled,
// triggerMode,
// isCompact,
// isAutoClose,
// isAutoPin,
// isFollowToolbar,
// isRemeberWinSize,
// actionItems,
// actionWindowOpacity,
// filterMode,
// filterList,
// setSelectionEnabled,
// setTriggerMode,
// setIsCompact,
// setIsAutoClose,
// setIsAutoPin,
// setIsFollowToolbar,
// setIsRemeberWinSize,
// setActionWindowOpacity,
// setActionItems,
// setFilterMode,
// setFilterList
// } = useSelectionAssistant()
const [selectionEnabled, setSelectionEnabled] = usePreference('feature.selection.enabled') const [selectionEnabled, setSelectionEnabled] = usePreference('feature.selection.enabled')
const [triggerMode, setTriggerMode] = usePreference('feature.selection.trigger_mode') const [triggerMode, setTriggerMode] = usePreference('feature.selection.trigger_mode')
@ -68,18 +40,6 @@ const SelectionAssistantSettings: FC = () => {
const [filterList, setFilterList] = usePreference('feature.selection.filter_list') const [filterList, setFilterList] = usePreference('feature.selection.filter_list')
const [actionItems, setActionItems] = usePreference('feature.selection.action_items') const [actionItems, setActionItems] = usePreference('feature.selection.action_items')
logger.debug(`selectionEnabled: ${selectionEnabled}`)
logger.debug(`triggerMode: ${triggerMode}`)
logger.debug(`isCompact: ${isCompact}`)
logger.debug(`isAutoClose: ${isAutoClose}`)
logger.debug(`isAutoPin: ${isAutoPin}`)
logger.debug(`isFollowToolbar: ${isFollowToolbar}`)
logger.debug(`isRemeberWinSize: ${isRemeberWinSize}`)
logger.debug(`actionWindowOpacity: ${actionWindowOpacity}`)
logger.debug(`filterMode: ${filterMode}`)
logger.debug(`filterList: ${filterList}`)
logger.debug(`actionItems: ${actionItems}`)
const isSupportedOS = isWin || isMac const isSupportedOS = isWin || isMac
const [isFilterListModalOpen, setIsFilterListModalOpen] = useState(false) const [isFilterListModalOpen, setIsFilterListModalOpen] = useState(false)
@ -168,7 +128,7 @@ const SelectionAssistantSettings: FC = () => {
</SettingLabel> </SettingLabel>
<Radio.Group <Radio.Group
value={triggerMode} value={triggerMode}
onChange={(e) => setTriggerMode(e.target.value as TriggerMode)} onChange={(e) => setTriggerMode(e.target.value as SelectionTriggerMode)}
buttonStyle="solid"> buttonStyle="solid">
<Tooltip placement="top" title={t('selection.settings.toolbar.trigger_mode.selected_note')} arrow> <Tooltip placement="top" title={t('selection.settings.toolbar.trigger_mode.selected_note')} arrow>
<Radio.Button value="selected">{t('selection.settings.toolbar.trigger_mode.selected')}</Radio.Button> <Radio.Button value="selected">{t('selection.settings.toolbar.trigger_mode.selected')}</Radio.Button>
@ -257,7 +217,7 @@ const SelectionAssistantSettings: FC = () => {
</SettingRow> </SettingRow>
</SettingGroup> </SettingGroup>
<SelectionActionsList actionItems={actionItems as ActionItem[]} setActionItems={setActionItems} /> <SelectionActionsList actionItems={actionItems} setActionItems={setActionItems} />
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
<SettingTitle>{t('selection.settings.advanced.title')}</SettingTitle> <SettingTitle>{t('selection.settings.advanced.title')}</SettingTitle>
@ -269,7 +229,7 @@ const SelectionAssistantSettings: FC = () => {
</SettingLabel> </SettingLabel>
<Radio.Group <Radio.Group
value={filterMode ?? 'default'} value={filterMode ?? 'default'}
onChange={(e) => setFilterMode(e.target.value as FilterMode)} onChange={(e) => setFilterMode(e.target.value as SelectionFilterMode)}
buttonStyle="solid"> buttonStyle="solid">
<Radio.Button value="default">{t('selection.settings.advanced.filter_mode.default')}</Radio.Button> <Radio.Button value="default">{t('selection.settings.advanced.filter_mode.default')}</Radio.Button>
<Radio.Button value="whitelist">{t('selection.settings.advanced.filter_mode.whitelist')}</Radio.Button> <Radio.Button value="whitelist">{t('selection.settings.advanced.filter_mode.whitelist')}</Radio.Button>

View File

@ -1,27 +0,0 @@
export type TriggerMode = 'selected' | 'ctrlkey' | 'shortcut'
export type FilterMode = 'default' | 'whitelist' | 'blacklist'
export interface ActionItem {
id: string
name: string
enabled: boolean
isBuiltIn: boolean
icon?: string
prompt?: string
assistantId?: string
selectedText?: string
searchEngine?: string
}
export interface SelectionState {
selectionEnabled: boolean
triggerMode: TriggerMode
isCompact: boolean
isAutoClose: boolean
isAutoPin: boolean
isFollowToolbar: boolean
isRemeberWinSize: boolean
filterMode: FilterMode
filterList: string[]
actionWindowOpacity: number
actionItems: ActionItem[]
}

View File

@ -1,9 +1,9 @@
import { usePreference } from '@data/hooks/usePreference'
import { isMac } from '@renderer/config/constant' import { isMac } from '@renderer/config/constant'
import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import type { ActionItem } from '@renderer/types/selectionTypes'
import { defaultLanguage } from '@shared/config/constant' import { defaultLanguage } from '@shared/config/constant'
import type { SelectionActionItem } from '@shared/data/types'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { Button, Slider, Tooltip } from 'antd' import { Button, Slider, Tooltip } from 'antd'
import { Droplet, Minus, Pin, X } from 'lucide-react' import { Droplet, Minus, Pin, X } from 'lucide-react'
@ -20,10 +20,13 @@ const SelectionActionApp: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const [action, setAction] = useState<ActionItem | null>(null) const [action, setAction] = useState<SelectionActionItem | null>(null)
const isActionLoaded = useRef(false) const isActionLoaded = useRef(false)
const { isAutoClose, isAutoPin, actionWindowOpacity } = useSelectionAssistant() const [isAutoClose] = usePreference('feature.selection.auto_close')
const [isAutoPin] = usePreference('feature.selection.auto_pin')
const [actionWindowOpacity] = usePreference('feature.selection.action_window_opacity')
const [isPinned, setIsPinned] = useState(isAutoPin) const [isPinned, setIsPinned] = useState(isAutoPin)
const [isWindowFocus, setIsWindowFocus] = useState(true) const [isWindowFocus, setIsWindowFocus] = useState(true)
@ -38,7 +41,7 @@ const SelectionActionApp: FC = () => {
useEffect(() => { useEffect(() => {
const actionListenRemover = window.electron?.ipcRenderer.on( const actionListenRemover = window.electron?.ipcRenderer.on(
IpcChannel.Selection_UpdateActionData, IpcChannel.Selection_UpdateActionData,
(_, actionItem: ActionItem) => { (_, actionItem: SelectionActionItem) => {
setAction(actionItem) setAction(actionItem)
isActionLoaded.current = true isActionLoaded.current = true
} }

View File

@ -11,8 +11,8 @@ import {
} from '@renderer/services/AssistantService' } from '@renderer/services/AssistantService'
import { pauseTrace } from '@renderer/services/SpanManagerService' import { pauseTrace } from '@renderer/services/SpanManagerService'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import type { ActionItem } from '@renderer/types/selectionTypes'
import { abortCompletion } from '@renderer/utils/abortController' import { abortCompletion } from '@renderer/utils/abortController'
import type { SelectionActionItem } from '@shared/data/types'
import { ChevronDown } from 'lucide-react' import { ChevronDown } from 'lucide-react'
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -22,7 +22,7 @@ import { processMessages } from './ActionUtils'
import WindowFooter from './WindowFooter' import WindowFooter from './WindowFooter'
interface Props { interface Props {
action: ActionItem action: SelectionActionItem
scrollToBottom?: () => void scrollToBottom?: () => void
} }

View File

@ -1,12 +1,12 @@
import '@renderer/assets/styles/selection-toolbar.scss' import '@renderer/assets/styles/selection-toolbar.scss'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { AppLogo } from '@renderer/config/env' import { AppLogo } from '@renderer/config/env'
import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import type { ActionItem } from '@renderer/types/selectionTypes'
import { defaultLanguage } from '@shared/config/constant' import { defaultLanguage } from '@shared/config/constant'
import type { SelectionActionItem } from '@shared/data/types'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { Avatar } from 'antd' import { Avatar } from 'antd'
import { ClipboardCheck, ClipboardCopy, ClipboardX, MessageSquareHeart } from 'lucide-react' import { ClipboardCheck, ClipboardCopy, ClipboardX, MessageSquareHeart } from 'lucide-react'
@ -32,9 +32,9 @@ const updateWindowSize = () => {
* ActionIcons is a component that renders the action icons * ActionIcons is a component that renders the action icons
*/ */
const ActionIcons: FC<{ const ActionIcons: FC<{
actionItems: ActionItem[] actionItems: SelectionActionItem[]
isCompact: boolean isCompact: boolean
handleAction: (action: ActionItem) => void handleAction: (action: SelectionActionItem) => void
copyIconStatus: 'normal' | 'success' | 'fail' copyIconStatus: 'normal' | 'success' | 'fail'
copyIconAnimation: 'none' | 'enter' | 'exit' copyIconAnimation: 'none' | 'enter' | 'exit'
}> = memo(({ actionItems, isCompact, handleAction, copyIconStatus, copyIconAnimation }) => { }> = memo(({ actionItems, isCompact, handleAction, copyIconStatus, copyIconAnimation }) => {
@ -67,7 +67,7 @@ const ActionIcons: FC<{
}, [copyIconStatus, copyIconAnimation]) }, [copyIconStatus, copyIconAnimation])
const renderActionButton = useCallback( const renderActionButton = useCallback(
(action: ActionItem) => { (action: SelectionActionItem) => {
const displayName = action.isBuiltIn ? t(action.name) : action.name const displayName = action.isBuiltIn ? t(action.name) : action.name
return ( return (
@ -99,7 +99,8 @@ const ActionIcons: FC<{
*/ */
const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => { const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => {
const { language, customCss } = useSettings() const { language, customCss } = useSettings()
const { isCompact, actionItems } = useSelectionAssistant() const [isCompact] = usePreference('feature.selection.compact')
const [actionItems] = usePreference('feature.selection.action_items')
const [animateKey, setAnimateKey] = useState(0) const [animateKey, setAnimateKey] = useState(0)
const [copyIconStatus, setCopyIconStatus] = useState<'normal' | 'success' | 'fail'>('normal') const [copyIconStatus, setCopyIconStatus] = useState<'normal' | 'success' | 'fail'>('normal')
const [copyIconAnimation, setCopyIconAnimation] = useState<'none' | 'enter' | 'exit'>('none') const [copyIconAnimation, setCopyIconAnimation] = useState<'none' | 'enter' | 'exit'>('none')
@ -179,7 +180,7 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => {
} }
const handleAction = useCallback( const handleAction = useCallback(
(action: ActionItem) => { (action: SelectionActionItem) => {
if (demo) return if (demo) return
/** avoid mutating the original action, it will cause syncing issue */ /** avoid mutating the original action, it will cause syncing issue */
@ -216,7 +217,7 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => {
} }
} }
const handleSearch = (action: ActionItem) => { const handleSearch = (action: SelectionActionItem) => {
if (!action.searchEngine) return if (!action.searchEngine) return
const customUrl = action.searchEngine.split('|')[1] const customUrl = action.searchEngine.split('|')[1]
@ -230,14 +231,14 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => {
/** /**
* Quote the selected text to the inputbar of the main window * Quote the selected text to the inputbar of the main window
*/ */
const handleQuote = (action: ActionItem) => { const handleQuote = (action: SelectionActionItem) => {
if (action.selectedText) { if (action.selectedText) {
window.api?.quoteToMainWindow(action.selectedText) window.api?.quoteToMainWindow(action.selectedText)
window.api?.selection.hideToolbar() window.api?.selection.hideToolbar()
} }
} }
const handleDefaultAction = (action: ActionItem) => { const handleDefaultAction = (action: SelectionActionItem) => {
// [macOS] only macOS has the available isFullscreen mode // [macOS] only macOS has the available isFullscreen mode
window.api?.selection.processAction(action, isFullScreen.current) window.api?.selection.processAction(action, isFullScreen.current)
window.api?.selection.hideToolbar() window.api?.selection.hideToolbar()