From 0fd9b6e56c4daf66cc36a991810dbe452d301252 Mon Sep 17 00:00:00 2001 From: fullex <106392080+0xfullex@users.noreply.github.com> Date: Wed, 2 Apr 2025 10:26:56 +0800 Subject: [PATCH] feat: miniWindow Pin/Resize (#3201) feat: [#2030] miniWindow pin/resizable/copy toast/move optimized --- src/main/ipc.ts | 1 + src/main/services/WindowService.ts | 19 ++++-- src/preload/index.d.ts | 1 + src/preload/index.ts | 3 +- src/renderer/src/i18n/locales/en-us.json | 10 ++- src/renderer/src/i18n/locales/ja-jp.json | 6 +- src/renderer/src/i18n/locales/ru-ru.json | 6 +- src/renderer/src/i18n/locales/zh-cn.json | 6 +- src/renderer/src/i18n/locales/zh-tw.json | 6 +- src/renderer/src/windows/mini/App.tsx | 6 ++ .../windows/mini/chat/components/Message.tsx | 3 +- .../windows/mini/chat/components/Messages.tsx | 1 + .../src/windows/mini/home/HomeWindow.tsx | 5 ++ .../mini/home/components/ClipboardPreview.tsx | 5 +- .../mini/home/components/FeatureMenus.tsx | 3 +- .../windows/mini/home/components/Footer.tsx | 62 ++++++++++++++++--- .../mini/translate/TranslateWindow.tsx | 9 ++- 17 files changed, 125 insertions(+), 27 deletions(-) diff --git a/src/main/ipc.ts b/src/main/ipc.ts index fa53f082..559e05e7 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -255,6 +255,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle('miniwindow:hide', () => windowService.hideMiniWindow()) ipcMain.handle('miniwindow:close', () => windowService.closeMiniWindow()) ipcMain.handle('miniwindow:toggle', () => windowService.toggleMiniWindow()) + ipcMain.handle('miniwindow:set-pin', (_, isPinned) => windowService.setPinMiniWindow(isPinned)) // aes ipcMain.handle('aes:encrypt', (_, text: string, secretKey: string, iv: string) => encrypt(text, secretKey, iv)) diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index f72c5b1b..0ab3b023 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -15,6 +15,7 @@ export class WindowService { private static instance: WindowService | null = null private mainWindow: BrowserWindow | null = null private miniWindow: BrowserWindow | null = null + private isPinnedMiniWindow: boolean = false private wasFullScreen: boolean = false //hacky-fix: store the focused status of mainWindow before miniWindow shows //to restore the focus status when miniWindow hides @@ -378,8 +379,12 @@ export class WindowService { public createMiniWindow(isPreload: boolean = false): BrowserWindow { this.miniWindow = new BrowserWindow({ - width: 500, - height: 520, + width: 550, + height: 400, + minWidth: 350, + minHeight: 380, + maxWidth: 1024, + maxHeight: 768, show: false, autoHideMenuBar: true, transparent: isMac, @@ -388,7 +393,7 @@ export class WindowService { center: true, frame: false, alwaysOnTop: true, - resizable: false, + resizable: true, useContentSize: true, ...(isMac ? { type: 'panel' } : {}), skipTaskbar: true, @@ -419,7 +424,9 @@ export class WindowService { }) this.miniWindow.on('blur', () => { - this.hideMiniWindow() + if (!this.isPinnedMiniWindow) { + this.hideMiniWindow() + } }) this.miniWindow.on('closed', () => { @@ -503,6 +510,10 @@ export class WindowService { this.showMiniWindow() } + public setPinMiniWindow(isPinned) { + this.isPinnedMiniWindow = isPinned + } + public showSelectionMenu(bounds: { x: number; y: number }) { if (this.selectionMenuWindow && !this.selectionMenuWindow.isDestroyed()) { this.selectionMenuWindow.setPosition(bounds.x, bounds.y) diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 1e2fb51a..aee54e55 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -137,6 +137,7 @@ declare global { hide: () => Promise close: () => Promise toggle: () => Promise + setPin: (isPinned: boolean) => Promise } aes: { encrypt: (text: string, secretKey: string, iv: string) => Promise<{ iv: string; encryptedData: string }> diff --git a/src/preload/index.ts b/src/preload/index.ts index 50aa5a2d..fe223e9f 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -112,7 +112,8 @@ const api = { show: () => ipcRenderer.invoke('miniwindow:show'), hide: () => ipcRenderer.invoke('miniwindow:hide'), close: () => ipcRenderer.invoke('miniwindow:close'), - toggle: () => ipcRenderer.invoke('miniwindow:toggle') + toggle: () => ipcRenderer.invoke('miniwindow:toggle'), + setPin: (isPinned: boolean) => ipcRenderer.invoke('miniwindow:set-pin', isPinned) }, aes: { encrypt: (text: string, secretKey: string, iv: string) => ipcRenderer.invoke('aes:encrypt', text, secretKey, iv), diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 31bd6b63..9daa0a4b 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -580,15 +580,19 @@ }, "footer": { "copy_last_message": "Press C to copy", - "esc": "Press ESC {{action}}", - "esc_back": "back", - "esc_close": "close the window" + "backspace_clear": "Backspace to clear", + "esc": "ESC to {{action}}", + "esc_back": "return", + "esc_close": "close" }, "input": { "placeholder": { "empty": "Ask {{model}} for help...", "title": "What do you want to do with this text?" } + }, + "tooltip": { + "pin": "Keep Window on Top" } }, "models": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 50764e48..c35f9b31 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -582,13 +582,17 @@ "copy_last_message": "C キーを押してコピー", "esc": "ESC キーを押して{{action}}", "esc_back": "戻る", - "esc_close": "ウィンドウを閉じる" + "esc_close": "ウィンドウを閉じる", + "backspace_clear": "バックスペースを押してクリアします" }, "input": { "placeholder": { "empty": "{{model}} に質問してください...", "title": "下のテキストに対して何をしますか?" } + }, + "tooltip": { + "pin": "上部ウィンドウ" } }, "models": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 15b0d4a5..6064f130 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -582,13 +582,17 @@ "copy_last_message": "Нажмите C для копирования", "esc": "Нажмите ESC {{action}}", "esc_back": "возвращения", - "esc_close": "закрытия окна" + "esc_close": "закрытия окна", + "backspace_clear": "Нажмите Backspace, чтобы очистить" }, "input": { "placeholder": { "empty": "Задайте вопрос {{model}}...", "title": "Что вы хотите сделать с этим текстом?" } + }, + "tooltip": { + "pin": "Верхнее окно" } }, "models": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index b5d0931f..58414f05 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -580,15 +580,19 @@ }, "footer": { "copy_last_message": "按 C 键复制", + "backspace_clear": "按 Backspace 清空", "esc": "按 ESC {{action}}", "esc_back": "返回", - "esc_close": "关闭窗口" + "esc_close": "关闭" }, "input": { "placeholder": { "empty": "询问 {{model}} 获取帮助...", "title": "你想对下方文字做什么" } + }, + "tooltip": { + "pin": "窗口置顶" } }, "models": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 41076e0a..32efdd73 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -582,13 +582,17 @@ "copy_last_message": "按 C 鍵複製", "esc": "按 ESC {{action}}", "esc_back": "返回", - "esc_close": "關閉視窗" + "esc_close": "關閉視窗", + "backspace_clear": "按 Backspace 清空" }, "input": { "placeholder": { "empty": "詢問 {{model}} 取得幫助...", "title": "你想對下方文字做什麼" } + }, + "tooltip": { + "pin": "窗口置頂" } }, "models": { diff --git a/src/renderer/src/windows/mini/App.tsx b/src/renderer/src/windows/mini/App.tsx index 17367a0c..41f06b8d 100644 --- a/src/renderer/src/windows/mini/App.tsx +++ b/src/renderer/src/windows/mini/App.tsx @@ -1,6 +1,7 @@ import '@renderer/databases' import store, { persistor } from '@renderer/store' +import { message } from 'antd' import { Provider } from 'react-redux' import { PersistGate } from 'redux-persist/integration/react' @@ -10,12 +11,17 @@ import { ThemeProvider } from '../../context/ThemeProvider' import HomeWindow from './home/HomeWindow' function MiniWindow(): React.ReactElement { + //miniWindow should register its own message component + const [messageApi, messageContextHolder] = message.useMessage() + window.message = messageApi + return ( + {messageContextHolder} diff --git a/src/renderer/src/windows/mini/chat/components/Message.tsx b/src/renderer/src/windows/mini/chat/components/Message.tsx index 4ff4f16b..07bc5026 100644 --- a/src/renderer/src/windows/mini/chat/components/Message.tsx +++ b/src/renderer/src/windows/mini/chat/components/Message.tsx @@ -38,7 +38,7 @@ const MessageItem: FC = ({ message: _message, index, total, route, onSetM const messageBackground = getMessageBackground(true, isAssistantMessage) - const maxWidth = isMiniWindow() ? '480px' : '100%' + const maxWidth = isMiniWindow() ? '800px' : '100%' useEffect(() => { if (onGetMessages && onSetMessages) { @@ -93,6 +93,7 @@ const MessageItem: FC = ({ message: _message, index, total, route, onSetM const MessageContainer = styled.div` display: flex; + width: 100%; flex-direction: column; position: relative; transition: background-color 0.3s ease; diff --git a/src/renderer/src/windows/mini/chat/components/Messages.tsx b/src/renderer/src/windows/mini/chat/components/Messages.tsx index a41583db..7d65dab9 100644 --- a/src/renderer/src/windows/mini/chat/components/Messages.tsx +++ b/src/renderer/src/windows/mini/chat/components/Messages.tsx @@ -77,6 +77,7 @@ const Messages: FC = ({ assistant, route }) => { const Container = styled(Scrollbar)` display: flex; flex-direction: column-reverse; + align-items: center; padding-bottom: 20px; overflow-x: hidden; min-width: 100%; diff --git a/src/renderer/src/windows/mini/home/HomeWindow.tsx b/src/renderer/src/windows/mini/home/HomeWindow.tsx index b3967577..74c42f7b 100644 --- a/src/renderer/src/windows/mini/home/HomeWindow.tsx +++ b/src/renderer/src/windows/mini/home/HomeWindow.tsx @@ -278,6 +278,8 @@ const HomeWindow: FC = () => {