mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-25 03:10:08 +08:00
feat(SelectionAssistant): App Filter / 应用筛选 (#6519)
feat: add filter mode and list functionality to selection assistant - Introduced new filter mode options (default, whitelist, blacklist) for the selection assistant. - Added methods to set and get filter mode and filter list in ConfigManager. - Enhanced SelectionService to manage filter mode and list, affecting text selection processing. - Updated UI components to allow users to configure filter settings. - Localized new filter settings in multiple languages.
This commit is contained in:
parent
94e6ba759e
commit
f83d9fc03c
@ -186,6 +186,8 @@ export enum IpcChannel {
|
||||
Selection_WriteToClipboard = 'selection:write-to-clipboard',
|
||||
Selection_SetEnabled = 'selection:set-enabled',
|
||||
Selection_SetTriggerMode = 'selection:set-trigger-mode',
|
||||
Selection_SetFilterMode = 'selection:set-filter-mode',
|
||||
Selection_SetFilterList = 'selection:set-filter-list',
|
||||
Selection_SetFollowToolbar = 'selection:set-follow-toolbar',
|
||||
Selection_ActionWindowClose = 'selection:action-window-close',
|
||||
Selection_ActionWindowMinimize = 'selection:action-window-minimize',
|
||||
|
||||
@ -19,7 +19,9 @@ export enum ConfigKeys {
|
||||
EnableDataCollection = 'enableDataCollection',
|
||||
SelectionAssistantEnabled = 'selectionAssistantEnabled',
|
||||
SelectionAssistantTriggerMode = 'selectionAssistantTriggerMode',
|
||||
SelectionAssistantFollowToolbar = 'selectionAssistantFollowToolbar'
|
||||
SelectionAssistantFollowToolbar = 'selectionAssistantFollowToolbar',
|
||||
SelectionAssistantFilterMode = 'selectionAssistantFilterMode',
|
||||
SelectionAssistantFilterList = 'selectionAssistantFilterList'
|
||||
}
|
||||
|
||||
export class ConfigManager {
|
||||
@ -173,6 +175,22 @@ export class ConfigManager {
|
||||
this.setAndNotify(ConfigKeys.SelectionAssistantFollowToolbar, value)
|
||||
}
|
||||
|
||||
getSelectionAssistantFilterMode(): string {
|
||||
return this.get<string>(ConfigKeys.SelectionAssistantFilterMode, 'default')
|
||||
}
|
||||
|
||||
setSelectionAssistantFilterMode(value: string) {
|
||||
this.setAndNotify(ConfigKeys.SelectionAssistantFilterMode, value)
|
||||
}
|
||||
|
||||
getSelectionAssistantFilterList(): string[] {
|
||||
return this.get<string[]>(ConfigKeys.SelectionAssistantFilterList, [])
|
||||
}
|
||||
|
||||
setSelectionAssistantFilterList(value: string[]) {
|
||||
this.setAndNotify(ConfigKeys.SelectionAssistantFilterList, value)
|
||||
}
|
||||
|
||||
setAndNotify(key: string, value: unknown) {
|
||||
this.set(key, value, true)
|
||||
}
|
||||
|
||||
@ -60,6 +60,8 @@ export class SelectionService {
|
||||
|
||||
private triggerMode = 'selected'
|
||||
private isFollowToolbar = true
|
||||
private filterMode = 'default'
|
||||
private filterList: string[] = []
|
||||
|
||||
private toolbarWindow: BrowserWindow | null = null
|
||||
private actionWindows = new Set<BrowserWindow>()
|
||||
@ -138,6 +140,10 @@ export class SelectionService {
|
||||
private initConfig() {
|
||||
this.triggerMode = configManager.getSelectionAssistantTriggerMode()
|
||||
this.isFollowToolbar = configManager.getSelectionAssistantFollowToolbar()
|
||||
this.filterMode = configManager.getSelectionAssistantFilterMode()
|
||||
this.filterList = configManager.getSelectionAssistantFilterList()
|
||||
|
||||
this.setHookClipboardMode(this.filterMode, this.filterList)
|
||||
|
||||
configManager.subscribe(ConfigKeys.SelectionAssistantTriggerMode, (triggerMode: string) => {
|
||||
this.triggerMode = triggerMode
|
||||
@ -147,6 +153,34 @@ export class SelectionService {
|
||||
configManager.subscribe(ConfigKeys.SelectionAssistantFollowToolbar, (isFollowToolbar: boolean) => {
|
||||
this.isFollowToolbar = isFollowToolbar
|
||||
})
|
||||
|
||||
configManager.subscribe(ConfigKeys.SelectionAssistantFilterMode, (filterMode: string) => {
|
||||
this.filterMode = filterMode
|
||||
this.setHookClipboardMode(this.filterMode, this.filterList)
|
||||
})
|
||||
|
||||
configManager.subscribe(ConfigKeys.SelectionAssistantFilterList, (filterList: string[]) => {
|
||||
this.filterList = filterList
|
||||
this.setHookClipboardMode(this.filterMode, this.filterList)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the clipboard mode for the selection-hook
|
||||
* @param mode - The mode to set, either 'default', 'whitelist', or 'blacklist'
|
||||
* @param list - An array of strings representing the list of items to include or exclude
|
||||
*/
|
||||
private setHookClipboardMode(mode: string, list: string[]) {
|
||||
if (!this.selectionHook) return
|
||||
|
||||
const modeMap = {
|
||||
default: 0,
|
||||
whitelist: 1,
|
||||
blacklist: 2
|
||||
}
|
||||
if (!this.selectionHook.setClipboardMode(modeMap[mode], list)) {
|
||||
this.logError(new Error('Failed to set selection-hook clipboard mode'))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -454,6 +488,30 @@ export class SelectionService {
|
||||
return startTop.y === endTop.y && startBottom.y === endBottom.y
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the text selection should be processed by filter mode&list
|
||||
* @param selectionData Text selection information and coordinates
|
||||
* @returns {boolean} True if the selection should be processed, false otherwise
|
||||
*/
|
||||
private shouldProcessTextSelection(selectionData: TextSelectionData): boolean {
|
||||
if (selectionData.programName === '' || this.filterMode === 'default') {
|
||||
return true
|
||||
}
|
||||
|
||||
const programName = selectionData.programName.toLowerCase()
|
||||
//items in filterList are already in lower case
|
||||
const isFound = this.filterList.some((item) => programName.includes(item))
|
||||
|
||||
switch (this.filterMode) {
|
||||
case 'whitelist':
|
||||
return isFound
|
||||
case 'blacklist':
|
||||
return !isFound
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Process text selection data and show toolbar
|
||||
* Handles different selection scenarios:
|
||||
@ -468,6 +526,10 @@ export class SelectionService {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.shouldProcessTextSelection(selectionData)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine reference point and position for toolbar
|
||||
let refPoint: { x: number; y: number } = { x: 0, y: 0 }
|
||||
let isLogical = false
|
||||
@ -946,6 +1008,14 @@ export class SelectionService {
|
||||
configManager.setSelectionAssistantFollowToolbar(isFollowToolbar)
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Selection_SetFilterMode, (_, filterMode: string) => {
|
||||
configManager.setSelectionAssistantFilterMode(filterMode)
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Selection_SetFilterList, (_, filterList: string[]) => {
|
||||
configManager.setSelectionAssistantFilterList(filterList)
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Selection_ProcessAction, (_, actionItem: ActionItem) => {
|
||||
selectionService?.processAction(actionItem)
|
||||
})
|
||||
|
||||
@ -218,6 +218,8 @@ const api = {
|
||||
setTriggerMode: (triggerMode: string) => ipcRenderer.invoke(IpcChannel.Selection_SetTriggerMode, triggerMode),
|
||||
setFollowToolbar: (isFollowToolbar: boolean) =>
|
||||
ipcRenderer.invoke(IpcChannel.Selection_SetFollowToolbar, isFollowToolbar),
|
||||
setFilterMode: (filterMode: string) => ipcRenderer.invoke(IpcChannel.Selection_SetFilterMode, filterMode),
|
||||
setFilterList: (filterList: string[]) => ipcRenderer.invoke(IpcChannel.Selection_SetFilterList, filterList),
|
||||
processAction: (actionItem: ActionItem) => ipcRenderer.invoke(IpcChannel.Selection_ProcessAction, actionItem),
|
||||
closeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowClose),
|
||||
minimizeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowMinimize),
|
||||
|
||||
@ -2,6 +2,8 @@ import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import {
|
||||
setActionItems,
|
||||
setActionWindowOpacity,
|
||||
setFilterList,
|
||||
setFilterMode,
|
||||
setIsAutoClose,
|
||||
setIsAutoPin,
|
||||
setIsCompact,
|
||||
@ -9,7 +11,7 @@ import {
|
||||
setSelectionEnabled,
|
||||
setTriggerMode
|
||||
} from '@renderer/store/selectionStore'
|
||||
import { ActionItem, TriggerMode } from '@renderer/types/selectionTypes'
|
||||
import { ActionItem, FilterMode, TriggerMode } from '@renderer/types/selectionTypes'
|
||||
|
||||
export function useSelectionAssistant() {
|
||||
const dispatch = useAppDispatch()
|
||||
@ -38,6 +40,14 @@ export function useSelectionAssistant() {
|
||||
dispatch(setIsFollowToolbar(isFollowToolbar))
|
||||
window.api.selection.setFollowToolbar(isFollowToolbar)
|
||||
},
|
||||
setFilterMode: (mode: FilterMode) => {
|
||||
dispatch(setFilterMode(mode))
|
||||
window.api.selection.setFilterMode(mode)
|
||||
},
|
||||
setFilterList: (list: string[]) => {
|
||||
dispatch(setFilterList(list))
|
||||
window.api.selection.setFilterList(list)
|
||||
},
|
||||
setActionWindowOpacity: (opacity: number) => {
|
||||
dispatch(setActionWindowOpacity(opacity))
|
||||
},
|
||||
|
||||
@ -1908,6 +1908,20 @@
|
||||
"delete_confirm": "Are you sure you want to delete this custom action?",
|
||||
"drag_hint": "Drag to reorder. Move above to enable action ({{enabled}}/{{max}})"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Advanced",
|
||||
"filter_mode": {
|
||||
"title": "Application Filter",
|
||||
"description": "Can limit the selection assistant to only work in specific applications (whitelist) or not work (blacklist)",
|
||||
"default": "Off",
|
||||
"whitelist": "Whitelist",
|
||||
"blacklist": "Blacklist"
|
||||
},
|
||||
"filter_list": {
|
||||
"title": "Filter List",
|
||||
"description": "Advanced feature, recommended for users with experience"
|
||||
}
|
||||
},
|
||||
"user_modal": {
|
||||
"title": {
|
||||
"add": "Add Custom Action",
|
||||
@ -1964,6 +1978,10 @@
|
||||
},
|
||||
"test": "Test"
|
||||
}
|
||||
},
|
||||
"filter_modal": {
|
||||
"title": "Application Filter List",
|
||||
"user_tips": "Please enter the executable file name of the application, one per line, case insensitive, can be fuzzy matched. For example: chrome.exe, weixin.exe, Cherry Studio.exe, etc."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1908,6 +1908,20 @@
|
||||
"delete_confirm": "このカスタム機能を削除しますか?",
|
||||
"drag_hint": "ドラッグで並べ替え (有効{{enabled}}/最大{{max}})"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "進階",
|
||||
"filter_mode": {
|
||||
"title": "アプリケーションフィルター",
|
||||
"description": "特定のアプリケーションでのみ選択ツールを有効にするか、無効にするかを選択できます。",
|
||||
"default": "オフ",
|
||||
"whitelist": "ホワイトリスト",
|
||||
"blacklist": "ブラックリスト"
|
||||
},
|
||||
"filter_list": {
|
||||
"title": "フィルターリスト",
|
||||
"description": "進階機能です。経験豊富なユーザー向けです。"
|
||||
}
|
||||
},
|
||||
"user_modal": {
|
||||
"title": {
|
||||
"add": "カスタム機能追加",
|
||||
@ -1964,6 +1978,10 @@
|
||||
},
|
||||
"test": "テスト"
|
||||
}
|
||||
},
|
||||
"filter_modal": {
|
||||
"title": "アプリケーションフィルターリスト",
|
||||
"user_tips": "アプリケーションの実行ファイル名を1行ずつ入力してください。大文字小文字は区別しません。例: chrome.exe, weixin.exe, Cherry Studio.exe, など。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1909,6 +1909,20 @@
|
||||
"delete_confirm": "Удалить это действие?",
|
||||
"drag_hint": "Перетащите для сортировки. Включено: {{enabled}}/{{max}}"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Расширенные",
|
||||
"filter_mode": {
|
||||
"title": "Режим фильтрации",
|
||||
"description": "Можно ограничить выборку по определенным приложениям (белый список) или исключить их (черный список)",
|
||||
"default": "Выключено",
|
||||
"whitelist": "Белый список",
|
||||
"blacklist": "Черный список"
|
||||
},
|
||||
"filter_list": {
|
||||
"title": "Список фильтрации",
|
||||
"description": "Расширенная функция, рекомендуется для пользователей с опытом"
|
||||
}
|
||||
},
|
||||
"user_modal": {
|
||||
"title": {
|
||||
"add": "Добавить действие",
|
||||
@ -1965,6 +1979,10 @@
|
||||
},
|
||||
"test": "Тест"
|
||||
}
|
||||
},
|
||||
"filter_modal": {
|
||||
"title": "Список фильтрации",
|
||||
"user_tips": "Введите имя исполняемого файла приложения, один на строку, не учитывая регистр, можно использовать подстановку *"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1908,6 +1908,20 @@
|
||||
"delete_confirm": "确定要删除这个自定义功能吗?",
|
||||
"drag_hint": "拖拽排序,移动到上方以启用功能 ({{enabled}}/{{max}})"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "高级",
|
||||
"filter_mode": {
|
||||
"title": "应用筛选",
|
||||
"description": "可以限制划词助手只在特定应用中生效(白名单)或不生效(黑名单)",
|
||||
"default": "关闭",
|
||||
"whitelist": "白名单",
|
||||
"blacklist": "黑名单"
|
||||
},
|
||||
"filter_list": {
|
||||
"title": "筛选名单",
|
||||
"description": "高级功能,建议有经验的用户在了解的情况下再进行设置"
|
||||
}
|
||||
},
|
||||
"user_modal": {
|
||||
"title": {
|
||||
"add": "添加自定义功能",
|
||||
@ -1964,6 +1978,10 @@
|
||||
},
|
||||
"test": "测试"
|
||||
}
|
||||
},
|
||||
"filter_modal": {
|
||||
"title": "应用筛选名单",
|
||||
"user_tips": "请输入应用的执行文件名,每行一个,不区分大小写,可以模糊匹配。例如:chrome.exe、weixin.exe、Cherry Studio.exe等"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1909,6 +1909,20 @@
|
||||
"delete_confirm": "確定要刪除這個自訂功能嗎?",
|
||||
"drag_hint": "拖曳排序,移動到上方以啟用功能 ({{enabled}}/{{max}})"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "進階",
|
||||
"filter_mode": {
|
||||
"title": "應用篩選",
|
||||
"description": "可以限制劃詞助手只在特定應用中生效(白名單)或不生效(黑名單)",
|
||||
"default": "關閉",
|
||||
"whitelist": "白名單",
|
||||
"blacklist": "黑名單"
|
||||
},
|
||||
"filter_list": {
|
||||
"title": "篩選名單",
|
||||
"description": "進階功能,建議有經驗的用戶在了解情況下再進行設置"
|
||||
}
|
||||
},
|
||||
"user_modal": {
|
||||
"title": {
|
||||
"add": "新增自訂功能",
|
||||
@ -1965,6 +1979,10 @@
|
||||
},
|
||||
"test": "測試"
|
||||
}
|
||||
},
|
||||
"filter_modal": {
|
||||
"title": "應用篩選名單",
|
||||
"user_tips": "請輸入應用的執行檔名稱,每行一個,不區分大小寫,可以模糊匹配。例如:chrome.exe、weixin.exe、Cherry Studio.exe等"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { isWindows } from '@renderer/config/constant'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant'
|
||||
import { TriggerMode } from '@renderer/types/selectionTypes'
|
||||
import { FilterMode, TriggerMode } from '@renderer/types/selectionTypes'
|
||||
import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar'
|
||||
import { Button, Radio, Row, Slider, Switch, Tooltip } from 'antd'
|
||||
import { CircleHelp } from 'lucide-react'
|
||||
import { FC, useEffect } from 'react'
|
||||
import { CircleHelp, Edit2 } from 'lucide-react'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
SettingTitle
|
||||
} from '..'
|
||||
import SelectionActionsList from './SelectionActionsList'
|
||||
import SelectionFilterListModal from './SelectionFilterListModal'
|
||||
|
||||
const SelectionAssistantSettings: FC = () => {
|
||||
const { theme } = useTheme()
|
||||
@ -32,6 +33,8 @@ const SelectionAssistantSettings: FC = () => {
|
||||
isFollowToolbar,
|
||||
actionItems,
|
||||
actionWindowOpacity,
|
||||
filterMode,
|
||||
filterList,
|
||||
setSelectionEnabled,
|
||||
setTriggerMode,
|
||||
setIsCompact,
|
||||
@ -39,8 +42,11 @@ const SelectionAssistantSettings: FC = () => {
|
||||
setIsAutoPin,
|
||||
setIsFollowToolbar,
|
||||
setActionWindowOpacity,
|
||||
setActionItems
|
||||
setActionItems,
|
||||
setFilterMode,
|
||||
setFilterList
|
||||
} = useSelectionAssistant()
|
||||
const [isFilterListModalOpen, setIsFilterListModalOpen] = useState(false)
|
||||
|
||||
// force disable selection assistant on non-windows systems
|
||||
useEffect(() => {
|
||||
@ -86,6 +92,7 @@ const SelectionAssistantSettings: FC = () => {
|
||||
<>
|
||||
<SettingGroup>
|
||||
<SettingTitle>{t('selection.settings.toolbar.title')}</SettingTitle>
|
||||
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
@ -106,7 +113,9 @@ const SelectionAssistantSettings: FC = () => {
|
||||
<Radio.Button value="ctrlkey">{t('selection.settings.toolbar.trigger_mode.ctrlkey')}</Radio.Button>
|
||||
</Radio.Group>
|
||||
</SettingRow>
|
||||
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
<SettingLabel>
|
||||
<SettingRowTitle>{t('selection.settings.toolbar.compact_mode.title')}</SettingRowTitle>
|
||||
@ -118,6 +127,7 @@ const SelectionAssistantSettings: FC = () => {
|
||||
|
||||
<SettingGroup>
|
||||
<SettingTitle>{t('selection.settings.window.title')}</SettingTitle>
|
||||
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
@ -127,7 +137,9 @@ const SelectionAssistantSettings: FC = () => {
|
||||
</SettingLabel>
|
||||
<Switch checked={isFollowToolbar} onChange={(checked) => setIsFollowToolbar(checked)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
<SettingLabel>
|
||||
<SettingRowTitle>{t('selection.settings.window.auto_close.title')}</SettingRowTitle>
|
||||
@ -135,7 +147,9 @@ const SelectionAssistantSettings: FC = () => {
|
||||
</SettingLabel>
|
||||
<Switch checked={isAutoClose} onChange={(checked) => setIsAutoClose(checked)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
<SettingLabel>
|
||||
<SettingRowTitle>{t('selection.settings.window.auto_pin.title')}</SettingRowTitle>
|
||||
@ -143,7 +157,9 @@ const SelectionAssistantSettings: FC = () => {
|
||||
</SettingLabel>
|
||||
<Switch checked={isAutoPin} onChange={(checked) => setIsAutoPin(checked)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
<SettingLabel>
|
||||
<SettingRowTitle>{t('selection.settings.window.opacity.title')}</SettingRowTitle>
|
||||
@ -163,6 +179,49 @@ const SelectionAssistantSettings: FC = () => {
|
||||
</SettingGroup>
|
||||
|
||||
<SelectionActionsList actionItems={actionItems} setActionItems={setActionItems} />
|
||||
|
||||
<SettingGroup>
|
||||
<SettingTitle>高级</SettingTitle>
|
||||
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
<SettingLabel>
|
||||
<SettingRowTitle>{t('selection.settings.advanced.filter_mode.title')}</SettingRowTitle>
|
||||
<SettingDescription>{t('selection.settings.advanced.filter_mode.description')}</SettingDescription>
|
||||
</SettingLabel>
|
||||
<Radio.Group
|
||||
value={filterMode}
|
||||
onChange={(e) => setFilterMode(e.target.value as FilterMode)}
|
||||
buttonStyle="solid">
|
||||
<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="blacklist">{t('selection.settings.advanced.filter_mode.blacklist')}</Radio.Button>
|
||||
</Radio.Group>
|
||||
</SettingRow>
|
||||
|
||||
{filterMode !== 'default' && (
|
||||
<>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingLabel>
|
||||
<SettingRowTitle>{t('selection.settings.advanced.filter_list.title')}</SettingRowTitle>
|
||||
<SettingDescription>{t('selection.settings.advanced.filter_list.description')}</SettingDescription>
|
||||
</SettingLabel>
|
||||
<Button icon={<Edit2 size={14} />} onClick={() => setIsFilterListModalOpen(true)}>
|
||||
{t('common.edit')}
|
||||
</Button>
|
||||
</SettingRow>
|
||||
|
||||
<SelectionFilterListModal
|
||||
open={isFilterListModalOpen}
|
||||
onClose={() => setIsFilterListModalOpen(false)}
|
||||
filterList={filterList}
|
||||
onSave={setFilterList}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</SettingGroup>
|
||||
</>
|
||||
)}
|
||||
</SettingContainer>
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
import { Button, Form, Input, Modal } from 'antd'
|
||||
import { FC, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface SelectionFilterListModalProps {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
filterList?: string[]
|
||||
onSave: (list: string[]) => void
|
||||
}
|
||||
|
||||
const SelectionFilterListModal: FC<SelectionFilterListModalProps> = ({ open, onClose, filterList = [], onSave }) => {
|
||||
const { t } = useTranslation()
|
||||
const [form] = Form.useForm()
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
form.setFieldsValue({
|
||||
filterList: (filterList || []).join('\n')
|
||||
})
|
||||
}
|
||||
}, [open, filterList, form])
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const values = await form.validateFields()
|
||||
const newList = values.filterList
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split('\n')
|
||||
.map((line: string) => line.trim())
|
||||
.filter((line: string) => line.length > 0)
|
||||
onSave(newList)
|
||||
onClose()
|
||||
} catch (error) {
|
||||
// validation failed
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('selection.settings.filter_modal.title')}
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
maskClosable={false}
|
||||
keyboard={true}
|
||||
destroyOnClose={true}
|
||||
footer={[
|
||||
<Button key="modal-cancel" onClick={onClose}>
|
||||
{t('common.cancel')}
|
||||
</Button>,
|
||||
<Button key="modal-save" type="primary" onClick={handleSave}>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
]}>
|
||||
<UserTip>{t('selection.settings.filter_modal.user_tips')}</UserTip>
|
||||
<Form form={form} layout="vertical" initialValues={{ filterList: '' }}>
|
||||
<Form.Item name="filterList" noStyle>
|
||||
<StyledTextArea autoSize={{ minRows: 6, maxRows: 16 }} autoFocus />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledTextArea = styled(Input.TextArea)`
|
||||
margin-top: 16px;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const UserTip = styled.div`
|
||||
font-size: 14px;
|
||||
`
|
||||
|
||||
export default SelectionFilterListModal
|
||||
@ -1,5 +1,5 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { ActionItem, SelectionState, TriggerMode } from '@renderer/types/selectionTypes'
|
||||
import { ActionItem, FilterMode, SelectionState, TriggerMode } from '@renderer/types/selectionTypes'
|
||||
|
||||
export const defaultActionItems: ActionItem[] = [
|
||||
{ id: 'translate', name: 'selection.action.builtin.translate', enabled: true, isBuiltIn: true, icon: 'languages' },
|
||||
@ -24,6 +24,8 @@ export const initialState: SelectionState = {
|
||||
isAutoClose: false,
|
||||
isAutoPin: false,
|
||||
isFollowToolbar: true,
|
||||
filterMode: 'default',
|
||||
filterList: [],
|
||||
actionWindowOpacity: 100,
|
||||
actionItems: defaultActionItems
|
||||
}
|
||||
@ -50,6 +52,12 @@ const selectionSlice = createSlice({
|
||||
setIsFollowToolbar: (state, action: PayloadAction<boolean>) => {
|
||||
state.isFollowToolbar = action.payload
|
||||
},
|
||||
setFilterMode: (state, action: PayloadAction<FilterMode>) => {
|
||||
state.filterMode = action.payload
|
||||
},
|
||||
setFilterList: (state, action: PayloadAction<string[]>) => {
|
||||
state.filterList = action.payload
|
||||
},
|
||||
setActionWindowOpacity: (state, action: PayloadAction<number>) => {
|
||||
state.actionWindowOpacity = action.payload
|
||||
},
|
||||
@ -66,6 +74,8 @@ export const {
|
||||
setIsAutoClose,
|
||||
setIsAutoPin,
|
||||
setIsFollowToolbar,
|
||||
setFilterMode,
|
||||
setFilterList,
|
||||
setActionWindowOpacity,
|
||||
setActionItems
|
||||
} = selectionSlice.actions
|
||||
|
||||
4
src/renderer/src/types/selectionTypes.d.ts
vendored
4
src/renderer/src/types/selectionTypes.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
export type TriggerMode = 'selected' | 'ctrlkey'
|
||||
|
||||
export type FilterMode = 'default' | 'whitelist' | 'blacklist'
|
||||
export interface ActionItem {
|
||||
id: string
|
||||
name: string
|
||||
@ -19,6 +19,8 @@ export interface SelectionState {
|
||||
isAutoClose: boolean
|
||||
isAutoPin: boolean
|
||||
isFollowToolbar: boolean
|
||||
filterMode: FilterMode
|
||||
filterList: string[]
|
||||
actionWindowOpacity: number
|
||||
actionItems: ActionItem[]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user