mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-26 20:12:38 +08:00
feat(SelectionAssistant): add "Remember Window Size" functionality
- Introduced a new setting to remember the last adjusted size of the action window. - Updated ConfigManager, SelectionService, and IPC channels to handle the new feature. - Enhanced UI components to allow users to toggle the "Remember Size" option. - Localized the new setting in multiple languages.
This commit is contained in:
parent
18017e9952
commit
d18436578d
@ -189,6 +189,7 @@ export enum IpcChannel {
|
||||
Selection_SetFilterMode = 'selection:set-filter-mode',
|
||||
Selection_SetFilterList = 'selection:set-filter-list',
|
||||
Selection_SetFollowToolbar = 'selection:set-follow-toolbar',
|
||||
Selection_SetRemeberWinSize = 'selection:set-remeber-win-size',
|
||||
Selection_ActionWindowClose = 'selection:action-window-close',
|
||||
Selection_ActionWindowMinimize = 'selection:action-window-minimize',
|
||||
Selection_ActionWindowPin = 'selection:action-window-pin',
|
||||
|
||||
@ -20,6 +20,7 @@ export enum ConfigKeys {
|
||||
SelectionAssistantEnabled = 'selectionAssistantEnabled',
|
||||
SelectionAssistantTriggerMode = 'selectionAssistantTriggerMode',
|
||||
SelectionAssistantFollowToolbar = 'selectionAssistantFollowToolbar',
|
||||
SelectionAssistantRemeberWinSize = 'selectionAssistantRemeberWinSize',
|
||||
SelectionAssistantFilterMode = 'selectionAssistantFilterMode',
|
||||
SelectionAssistantFilterList = 'selectionAssistantFilterList'
|
||||
}
|
||||
@ -175,6 +176,14 @@ export class ConfigManager {
|
||||
this.setAndNotify(ConfigKeys.SelectionAssistantFollowToolbar, value)
|
||||
}
|
||||
|
||||
getSelectionAssistantRemeberWinSize(): boolean {
|
||||
return this.get<boolean>(ConfigKeys.SelectionAssistantRemeberWinSize, false)
|
||||
}
|
||||
|
||||
setSelectionAssistantRemeberWinSize(value: boolean) {
|
||||
this.setAndNotify(ConfigKeys.SelectionAssistantRemeberWinSize, value)
|
||||
}
|
||||
|
||||
getSelectionAssistantFilterMode(): string {
|
||||
return this.get<string>(ConfigKeys.SelectionAssistantFilterMode, 'default')
|
||||
}
|
||||
|
||||
@ -60,6 +60,7 @@ export class SelectionService {
|
||||
|
||||
private triggerMode = 'selected'
|
||||
private isFollowToolbar = true
|
||||
private isRemeberWinSize = false
|
||||
private filterMode = 'default'
|
||||
private filterList: string[] = []
|
||||
|
||||
@ -86,6 +87,11 @@ export class SelectionService {
|
||||
private readonly ACTION_WINDOW_WIDTH = 500
|
||||
private readonly ACTION_WINDOW_HEIGHT = 400
|
||||
|
||||
private lastActionWindowSize: { width: number; height: number } = {
|
||||
width: this.ACTION_WINDOW_WIDTH,
|
||||
height: this.ACTION_WINDOW_HEIGHT
|
||||
}
|
||||
|
||||
private constructor() {
|
||||
try {
|
||||
if (!SelectionHook) {
|
||||
@ -140,6 +146,7 @@ export class SelectionService {
|
||||
private initConfig() {
|
||||
this.triggerMode = configManager.getSelectionAssistantTriggerMode()
|
||||
this.isFollowToolbar = configManager.getSelectionAssistantFollowToolbar()
|
||||
this.isRemeberWinSize = configManager.getSelectionAssistantRemeberWinSize()
|
||||
this.filterMode = configManager.getSelectionAssistantFilterMode()
|
||||
this.filterList = configManager.getSelectionAssistantFilterList()
|
||||
|
||||
@ -154,6 +161,17 @@ export class SelectionService {
|
||||
this.isFollowToolbar = isFollowToolbar
|
||||
})
|
||||
|
||||
configManager.subscribe(ConfigKeys.SelectionAssistantRemeberWinSize, (isRemeberWinSize: boolean) => {
|
||||
this.isRemeberWinSize = isRemeberWinSize
|
||||
//when off, reset the last action window size to default
|
||||
if (!this.isRemeberWinSize) {
|
||||
this.lastActionWindowSize = {
|
||||
width: this.ACTION_WINDOW_WIDTH,
|
||||
height: this.ACTION_WINDOW_HEIGHT
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
configManager.subscribe(ConfigKeys.SelectionAssistantFilterMode, (filterMode: string) => {
|
||||
this.filterMode = filterMode
|
||||
this.setHookGlobalFilterMode(this.filterMode, this.filterList)
|
||||
@ -810,8 +828,8 @@ export class SelectionService {
|
||||
*/
|
||||
private createPreloadedActionWindow(): BrowserWindow {
|
||||
const preloadedActionWindow = new BrowserWindow({
|
||||
width: this.ACTION_WINDOW_WIDTH,
|
||||
height: this.ACTION_WINDOW_HEIGHT,
|
||||
width: this.isRemeberWinSize ? this.lastActionWindowSize.width : this.ACTION_WINDOW_WIDTH,
|
||||
height: this.isRemeberWinSize ? this.lastActionWindowSize.height : this.ACTION_WINDOW_HEIGHT,
|
||||
minWidth: 300,
|
||||
minHeight: 200,
|
||||
frame: false,
|
||||
@ -885,6 +903,16 @@ export class SelectionService {
|
||||
}
|
||||
})
|
||||
|
||||
//remember the action window size
|
||||
actionWindow.on('resized', () => {
|
||||
if (this.isRemeberWinSize) {
|
||||
this.lastActionWindowSize = {
|
||||
width: actionWindow.getBounds().width,
|
||||
height: actionWindow.getBounds().height
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.actionWindows.add(actionWindow)
|
||||
|
||||
// Asynchronously create a new preloaded window
|
||||
@ -907,30 +935,58 @@ export class SelectionService {
|
||||
* @param actionWindow Window to position and show
|
||||
*/
|
||||
private showActionWindow(actionWindow: BrowserWindow) {
|
||||
let actionWindowWidth = this.ACTION_WINDOW_WIDTH
|
||||
let actionWindowHeight = this.ACTION_WINDOW_HEIGHT
|
||||
|
||||
//if remember win size is true, use the last remembered size
|
||||
if (this.isRemeberWinSize) {
|
||||
actionWindowWidth = this.lastActionWindowSize.width
|
||||
actionWindowHeight = this.lastActionWindowSize.height
|
||||
}
|
||||
|
||||
//center way
|
||||
if (!this.isFollowToolbar || !this.toolbarWindow) {
|
||||
if (this.isRemeberWinSize) {
|
||||
actionWindow.setBounds({
|
||||
width: actionWindowWidth,
|
||||
height: actionWindowHeight
|
||||
})
|
||||
}
|
||||
|
||||
actionWindow.show()
|
||||
this.hideToolbar()
|
||||
return
|
||||
}
|
||||
|
||||
//follow toolbar
|
||||
|
||||
const toolbarBounds = this.toolbarWindow!.getBounds()
|
||||
const display = screen.getDisplayNearestPoint({ x: toolbarBounds.x, y: toolbarBounds.y })
|
||||
const workArea = display.workArea
|
||||
const GAP = 6 // 6px gap from screen edges
|
||||
|
||||
//make sure action window is inside screen
|
||||
if (actionWindowWidth > workArea.width - 2 * GAP) {
|
||||
actionWindowWidth = workArea.width - 2 * GAP
|
||||
}
|
||||
|
||||
if (actionWindowHeight > workArea.height - 2 * GAP) {
|
||||
actionWindowHeight = workArea.height - 2 * GAP
|
||||
}
|
||||
|
||||
// Calculate initial position to center action window horizontally below toolbar
|
||||
let posX = Math.round(toolbarBounds.x + (toolbarBounds.width - this.ACTION_WINDOW_WIDTH) / 2)
|
||||
let posX = Math.round(toolbarBounds.x + (toolbarBounds.width - actionWindowWidth) / 2)
|
||||
let posY = Math.round(toolbarBounds.y)
|
||||
|
||||
// Ensure action window stays within screen boundaries with a small gap
|
||||
if (posX + this.ACTION_WINDOW_WIDTH > workArea.x + workArea.width) {
|
||||
posX = workArea.x + workArea.width - this.ACTION_WINDOW_WIDTH - GAP
|
||||
if (posX + actionWindowWidth > workArea.x + workArea.width) {
|
||||
posX = workArea.x + workArea.width - actionWindowWidth - GAP
|
||||
} else if (posX < workArea.x) {
|
||||
posX = workArea.x + GAP
|
||||
}
|
||||
if (posY + this.ACTION_WINDOW_HEIGHT > workArea.y + workArea.height) {
|
||||
if (posY + actionWindowHeight > workArea.y + workArea.height) {
|
||||
// If window would go below screen, try to position it above toolbar
|
||||
posY = workArea.y + workArea.height - this.ACTION_WINDOW_HEIGHT - GAP
|
||||
posY = workArea.y + workArea.height - actionWindowHeight - GAP
|
||||
} else if (posY < workArea.y) {
|
||||
posY = workArea.y + GAP
|
||||
}
|
||||
@ -938,8 +994,8 @@ export class SelectionService {
|
||||
actionWindow.setPosition(posX, posY, false)
|
||||
//KEY to make window not resize
|
||||
actionWindow.setBounds({
|
||||
width: this.ACTION_WINDOW_WIDTH,
|
||||
height: this.ACTION_WINDOW_HEIGHT,
|
||||
width: actionWindowWidth,
|
||||
height: actionWindowHeight,
|
||||
x: posX,
|
||||
y: posY
|
||||
})
|
||||
@ -1021,6 +1077,10 @@ export class SelectionService {
|
||||
configManager.setSelectionAssistantFollowToolbar(isFollowToolbar)
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Selection_SetRemeberWinSize, (_, isRemeberWinSize: boolean) => {
|
||||
configManager.setSelectionAssistantRemeberWinSize(isRemeberWinSize)
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Selection_SetFilterMode, (_, filterMode: string) => {
|
||||
configManager.setSelectionAssistantFilterMode(filterMode)
|
||||
})
|
||||
|
||||
@ -218,6 +218,8 @@ const api = {
|
||||
setTriggerMode: (triggerMode: string) => ipcRenderer.invoke(IpcChannel.Selection_SetTriggerMode, triggerMode),
|
||||
setFollowToolbar: (isFollowToolbar: boolean) =>
|
||||
ipcRenderer.invoke(IpcChannel.Selection_SetFollowToolbar, isFollowToolbar),
|
||||
setRemeberWinSize: (isRemeberWinSize: boolean) =>
|
||||
ipcRenderer.invoke(IpcChannel.Selection_SetRemeberWinSize, isRemeberWinSize),
|
||||
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),
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
setIsAutoPin,
|
||||
setIsCompact,
|
||||
setIsFollowToolbar,
|
||||
setIsRemeberWinSize,
|
||||
setSelectionEnabled,
|
||||
setTriggerMode
|
||||
} from '@renderer/store/selectionStore'
|
||||
@ -40,6 +41,10 @@ export function useSelectionAssistant() {
|
||||
dispatch(setIsFollowToolbar(isFollowToolbar))
|
||||
window.api.selection.setFollowToolbar(isFollowToolbar)
|
||||
},
|
||||
setIsRemeberWinSize: (isRemeberWinSize: boolean) => {
|
||||
dispatch(setIsRemeberWinSize(isRemeberWinSize))
|
||||
window.api.selection.setRemeberWinSize(isRemeberWinSize)
|
||||
},
|
||||
setFilterMode: (mode: FilterMode) => {
|
||||
dispatch(setFilterMode(mode))
|
||||
window.api.selection.setFilterMode(mode)
|
||||
|
||||
@ -1880,6 +1880,10 @@
|
||||
"title": "Follow Toolbar",
|
||||
"description": "Window position will follow the toolbar. When disabled, it will always be centered."
|
||||
},
|
||||
"remember_size": {
|
||||
"title": "Remember Size",
|
||||
"description": "Window will display at the last adjusted size during the application running"
|
||||
},
|
||||
"auto_close": {
|
||||
"title": "Auto Close",
|
||||
"description": "Automatically close the window when it's not pinned and loses focus"
|
||||
|
||||
@ -1880,6 +1880,10 @@
|
||||
"title": "ツールバーに追従",
|
||||
"description": "ウィンドウ位置をツールバーに連動(無効時は中央表示)"
|
||||
},
|
||||
"remember_size": {
|
||||
"title": "サイズを記憶",
|
||||
"description": "アプリケーション実行中、ウィンドウは最後に調整されたサイズで表示されます"
|
||||
},
|
||||
"auto_close": {
|
||||
"title": "自動閉じる",
|
||||
"description": "最前面固定されていない場合、フォーカス喪失時に自動閉じる"
|
||||
|
||||
@ -1880,6 +1880,10 @@
|
||||
"title": "Следовать за панелью",
|
||||
"description": "Окно будет следовать за панелью. Иначе - по центру."
|
||||
},
|
||||
"remember_size": {
|
||||
"title": "Запомнить размер",
|
||||
"description": "При отключенном режиме, окно будет восстанавливаться до последнего размера при запуске приложения"
|
||||
},
|
||||
"auto_close": {
|
||||
"title": "Автозакрытие",
|
||||
"description": "Закрывать окно при потере фокуса (если не закреплено)"
|
||||
|
||||
@ -1880,6 +1880,10 @@
|
||||
"title": "跟随工具栏",
|
||||
"description": "窗口位置将跟随工具栏显示,禁用后则始终居中显示"
|
||||
},
|
||||
"remember_size": {
|
||||
"title": "记住大小",
|
||||
"description": "应用运行期间,窗口会按上次调整的大小显示"
|
||||
},
|
||||
"auto_close": {
|
||||
"title": "自动关闭",
|
||||
"description": "当窗口未置顶且失去焦点时,将自动关闭该窗口"
|
||||
|
||||
@ -1880,6 +1880,10 @@
|
||||
"title": "跟隨工具列",
|
||||
"description": "視窗位置將跟隨工具列顯示,停用後則始終置中顯示"
|
||||
},
|
||||
"remember_size": {
|
||||
"title": "記住大小",
|
||||
"description": "應用運行期間,視窗會按上次調整的大小顯示"
|
||||
},
|
||||
"auto_close": {
|
||||
"title": "自動關閉",
|
||||
"description": "當視窗未置頂且失去焦點時,將自動關閉該視窗"
|
||||
|
||||
@ -31,6 +31,7 @@ const SelectionAssistantSettings: FC = () => {
|
||||
isAutoClose,
|
||||
isAutoPin,
|
||||
isFollowToolbar,
|
||||
isRemeberWinSize,
|
||||
actionItems,
|
||||
actionWindowOpacity,
|
||||
filterMode,
|
||||
@ -41,6 +42,7 @@ const SelectionAssistantSettings: FC = () => {
|
||||
setIsAutoClose,
|
||||
setIsAutoPin,
|
||||
setIsFollowToolbar,
|
||||
setIsRemeberWinSize,
|
||||
setActionWindowOpacity,
|
||||
setActionItems,
|
||||
setFilterMode,
|
||||
@ -140,6 +142,16 @@ const SelectionAssistantSettings: FC = () => {
|
||||
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
<SettingLabel>
|
||||
<SettingRowTitle>{t('selection.settings.window.remember_size.title')}</SettingRowTitle>
|
||||
<SettingDescription>{t('selection.settings.window.remember_size.description')}</SettingDescription>
|
||||
</SettingLabel>
|
||||
<Switch checked={isRemeberWinSize} onChange={(checked) => setIsRemeberWinSize(checked)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
<SettingLabel>
|
||||
<SettingRowTitle>{t('selection.settings.window.auto_close.title')}</SettingRowTitle>
|
||||
@ -191,7 +203,7 @@ const SelectionAssistantSettings: FC = () => {
|
||||
<SettingDescription>{t('selection.settings.advanced.filter_mode.description')}</SettingDescription>
|
||||
</SettingLabel>
|
||||
<Radio.Group
|
||||
value={filterMode}
|
||||
value={filterMode ?? 'default'}
|
||||
onChange={(e) => setFilterMode(e.target.value as FilterMode)}
|
||||
buttonStyle="solid">
|
||||
<Radio.Button value="default">{t('selection.settings.advanced.filter_mode.default')}</Radio.Button>
|
||||
@ -200,7 +212,7 @@ const SelectionAssistantSettings: FC = () => {
|
||||
</Radio.Group>
|
||||
</SettingRow>
|
||||
|
||||
{filterMode !== 'default' && (
|
||||
{filterMode && filterMode !== 'default' && (
|
||||
<>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
|
||||
@ -24,6 +24,7 @@ export const initialState: SelectionState = {
|
||||
isAutoClose: false,
|
||||
isAutoPin: false,
|
||||
isFollowToolbar: true,
|
||||
isRemeberWinSize: false,
|
||||
filterMode: 'default',
|
||||
filterList: [],
|
||||
actionWindowOpacity: 100,
|
||||
@ -52,6 +53,9 @@ const selectionSlice = createSlice({
|
||||
setIsFollowToolbar: (state, action: PayloadAction<boolean>) => {
|
||||
state.isFollowToolbar = action.payload
|
||||
},
|
||||
setIsRemeberWinSize: (state, action: PayloadAction<boolean>) => {
|
||||
state.isRemeberWinSize = action.payload
|
||||
},
|
||||
setFilterMode: (state, action: PayloadAction<FilterMode>) => {
|
||||
state.filterMode = action.payload
|
||||
},
|
||||
@ -74,6 +78,7 @@ export const {
|
||||
setIsAutoClose,
|
||||
setIsAutoPin,
|
||||
setIsFollowToolbar,
|
||||
setIsRemeberWinSize,
|
||||
setFilterMode,
|
||||
setFilterList,
|
||||
setActionWindowOpacity,
|
||||
|
||||
1
src/renderer/src/types/selectionTypes.d.ts
vendored
1
src/renderer/src/types/selectionTypes.d.ts
vendored
@ -19,6 +19,7 @@ export interface SelectionState {
|
||||
isAutoClose: boolean
|
||||
isAutoPin: boolean
|
||||
isFollowToolbar: boolean
|
||||
isRemeberWinSize: boolean
|
||||
filterMode: FilterMode
|
||||
filterList: string[]
|
||||
actionWindowOpacity: number
|
||||
|
||||
@ -221,10 +221,12 @@ const SelectionActionApp: FC = () => {
|
||||
<WinButton type="text" icon={<X size={16} />} onClick={handleClose} className="close" />
|
||||
</TitleBarButtons>
|
||||
</TitleBar>
|
||||
<Content ref={contentElementRef}>
|
||||
{action.id == 'translate' && <ActionTranslate action={action} scrollToBottom={handleScrollToBottom} />}
|
||||
{action.id != 'translate' && <ActionGeneral action={action} scrollToBottom={handleScrollToBottom} />}
|
||||
</Content>
|
||||
<MainContainer>
|
||||
<Content ref={contentElementRef}>
|
||||
{action.id == 'translate' && <ActionTranslate action={action} scrollToBottom={handleScrollToBottom} />}
|
||||
{action.id != 'translate' && <ActionGeneral action={action} scrollToBottom={handleScrollToBottom} />}
|
||||
</Content>
|
||||
</MainContainer>
|
||||
</WindowFrame>
|
||||
)
|
||||
}
|
||||
@ -340,6 +342,14 @@ const WinButton = styled(Button)`
|
||||
}
|
||||
`
|
||||
|
||||
const MainContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
`
|
||||
|
||||
const Content = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -349,7 +359,8 @@ const Content = styled.div`
|
||||
font-size: 14px;
|
||||
-webkit-app-region: none;
|
||||
user-select: text;
|
||||
width: 100%;
|
||||
/* width: 100%; */
|
||||
max-width: 1280px;
|
||||
`
|
||||
|
||||
const OpacitySlider = styled.div`
|
||||
|
||||
@ -266,13 +266,11 @@ const Container = styled.div`
|
||||
const Result = styled.div`
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
`
|
||||
|
||||
const MenuContainer = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
@ -309,7 +307,6 @@ const OriginalContent = styled.div`
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
`
|
||||
|
||||
const OriginalContentCopyWrapper = styled.div`
|
||||
|
||||
@ -155,7 +155,6 @@ const Result = styled.div`
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
`
|
||||
|
||||
const MenuContainer = styled.div`
|
||||
@ -164,7 +163,6 @@ const MenuContainer = styled.div`
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 960px;
|
||||
`
|
||||
|
||||
const OriginalHeader = styled.div`
|
||||
@ -198,7 +196,6 @@ const OriginalContent = styled.div`
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
`
|
||||
|
||||
const OriginalContentCopyWrapper = styled.div`
|
||||
|
||||
Loading…
Reference in New Issue
Block a user