feat: tray internationalization and shortcut display (#259)

This commit is contained in:
ayangweb
2025-03-07 12:42:55 +08:00
committed by GitHub
parent 58ce845fd2
commit a0b5867c9c
11 changed files with 169 additions and 27 deletions

View File

@@ -21,6 +21,7 @@
"@tauri-apps/plugin-global-shortcut": "~2.0.0",
"@tauri-apps/plugin-http": "~2.0.2",
"@tauri-apps/plugin-os": "^2.2.0",
"@tauri-apps/plugin-process": "^2.2.0",
"@tauri-apps/plugin-shell": "^2.2.0",
"@tauri-apps/plugin-updater": "^2.5.1",
"@tauri-apps/plugin-websocket": "~2.3.0",

10
pnpm-lock.yaml generated
View File

@@ -32,6 +32,9 @@ importers:
'@tauri-apps/plugin-os':
specifier: ^2.2.0
version: 2.2.0
'@tauri-apps/plugin-process':
specifier: ^2.2.0
version: 2.2.0
'@tauri-apps/plugin-shell':
specifier: ^2.2.0
version: 2.2.0
@@ -1098,6 +1101,9 @@ packages:
'@tauri-apps/plugin-os@2.2.0':
resolution: {integrity: sha512-HszbCdbisMlu5QhCNAN8YIWyz2v33abAWha6+uvV2CKX8P5VSct/y+kEe22JeyqrxCnWlQ3DRx7s49Byg7/0EA==}
'@tauri-apps/plugin-process@2.2.0':
resolution: {integrity: sha512-uypN2Crmyop9z+KRJr3zl71OyVFgTuvHFjsJ0UxxQ/J5212jVa5w4nPEYjIewcn8bUEXacRebwE6F7owgrbhSw==}
'@tauri-apps/plugin-shell@2.2.0':
resolution: {integrity: sha512-iC3Ic1hLmasoboG7BO+7p+AriSoqAwKrIk+Hpk+S/bjTQdXqbl2GbdclghI4gM32X0bls7xHzIFqhRdrlvJeaA==}
@@ -4138,6 +4144,10 @@ snapshots:
dependencies:
'@tauri-apps/api': 2.3.0
'@tauri-apps/plugin-process@2.2.0':
dependencies:
'@tauri-apps/api': 2.3.0
'@tauri-apps/plugin-shell@2.2.0':
dependencies:
'@tauri-apps/api': 2.3.0

View File

@@ -66,6 +66,7 @@
"fs-pro:default",
"macos-permissions:default",
"screenshots:default",
"core:window:allow-set-theme"
"core:window:allow-set-theme",
"process:default"
]
}

View File

@@ -82,7 +82,8 @@ pub fn run() {
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs_pro::init())
.plugin(tauri_plugin_macos_permissions::init())
.plugin(tauri_plugin_screenshots::init());
.plugin(tauri_plugin_screenshots::init())
.plugin(tauri_plugin_process::init());
// Conditional compilation for macOS
#[cfg(target_os = "macos")]
@@ -97,7 +98,9 @@ pub fn run() {
shortcut::unregister_shortcut,
shortcut::get_current_shortcut,
change_autostart,
show_coco,
hide_coco,
show_settings,
server::servers::get_server_token,
server::servers::add_coco_server,
server::servers::remove_coco_server,
@@ -143,7 +146,7 @@ pub fn run() {
});
shortcut::enable_shortcut(&app);
enable_tray(app);
// enable_tray(app);
enable_autostart(app);
#[cfg(target_os = "macos")]
@@ -239,6 +242,11 @@ async fn init_app_search_source<R: Runtime>(app_handle: &AppHandle<R>) -> Result
Ok(())
}
#[tauri::command]
async fn show_coco(app_handle: AppHandle) {
handle_open_coco(&app_handle);
}
#[tauri::command]
fn hide_coco(app: tauri::AppHandle) {
if let Some(window) = app.get_window(MAIN_WINDOW_LABEL) {
@@ -475,3 +483,8 @@ async fn get_app_search_source<R: Runtime>(app_handle: AppHandle<R>) -> Result<(
Ok(())
}
#[tauri::command]
async fn show_settings(app_handle: AppHandle) {
open_settings(&app_handle);
}

View File

@@ -113,6 +113,14 @@ export default function GeneralSettings() {
const [shortcut, setShortcut] = useState<Shortcut>([]);
const setShowCocoShortcuts = useAppStore((state) => {
return state.setShowCocoShortcuts;
});
useEffect(() => {
setShowCocoShortcuts(shortcut);
}, [shortcut]);
async function getCurrentShortcut() {
try {
const res: any = await invoke("get_current_shortcut");

88
src/hooks/useTray.ts Normal file
View File

@@ -0,0 +1,88 @@
import { useTranslation } from "react-i18next";
import { TrayIcon, type TrayIconOptions } from "@tauri-apps/api/tray";
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
import { isMac } from "@/utils/platform";
import { resolveResource } from "@tauri-apps/api/path";
import { useUpdateEffect } from "ahooks";
import { exit } from "@tauri-apps/plugin-process";
import { invoke } from "@tauri-apps/api/core";
import { useAppStore } from "@/stores/appStore";
const TRAY_ID = "COCO_TRAY";
export const useTray = () => {
const { t, i18n } = useTranslation();
const showCocoShortcuts = useAppStore((state) => state.showCocoShortcuts);
useUpdateEffect(() => {
if (showCocoShortcuts.length === 0) return;
updateTrayMenu();
}, [i18n.language, showCocoShortcuts]);
const getTrayById = () => {
return TrayIcon.getById(TRAY_ID);
};
const createTrayIcon = async () => {
const tray = await getTrayById();
if (tray) return;
const menu = await getTrayMenu();
const iconPath = isMac ? "assets/tray-mac.ico" : "assets/tray.ico";
const icon = await resolveResource(iconPath);
const options: TrayIconOptions = {
menu,
icon,
id: TRAY_ID,
iconAsTemplate: true,
};
return TrayIcon.new(options);
};
const getTrayMenu = async () => {
const items = await Promise.all([
MenuItem.new({
text: t("tray.showCoco"),
accelerator: showCocoShortcuts.join("+"),
action: () => {
invoke("show_coco");
},
}),
PredefinedMenuItem.new({ item: "Separator" }),
MenuItem.new({
text: t("tray.settings"),
// accelerator: "CommandOrControl+,",
action: () => {
invoke("show_settings");
},
}),
PredefinedMenuItem.new({ item: "Separator" }),
MenuItem.new({
text: t("tray.quitCoco"),
accelerator: "CommandOrControl+Q",
action: () => {
exit(0);
},
}),
]);
return Menu.new({ items });
};
const updateTrayMenu = async () => {
const tray = await getTrayById();
if (!tray) {
return createTrayIcon();
}
const menu = await getTrayMenu();
tray.setMenu(menu);
};
};

View File

@@ -201,5 +201,10 @@
},
"enable_server": "Enable Server",
"disable_server": "Disable Server"
},
"tray": {
"showCoco": "Show Coco",
"settings": "Settings...",
"quitCoco": "Quit Coco"
}
}

View File

@@ -201,5 +201,10 @@
},
"enable_server": "启用服务",
"disable_server": "禁用服务"
},
"tray": {
"showCoco": "显示 Coco",
"settings": "偏好设置",
"quitCoco": "退出 Coco"
}
}

View File

@@ -10,7 +10,6 @@ import ChatAI, { ChatAIRef } from "@/components/Assistant/Chat";
import { useAppStore } from "@/stores/appStore";
import { useAuthStore } from "@/stores/authStore";
import { isWin } from "@/utils/platform";
import { useMount } from "ahooks";
export default function DesktopApp() {
const initializeListeners = useAppStore((state) => state.initializeListeners);
@@ -20,9 +19,9 @@ export default function DesktopApp() {
const isPinned = useAppStore((state) => state.isPinned);
useMount(() => {
useEffect(() => {
invoke("get_app_search_source");
});
}, []);
useEffect(() => {
initializeListeners();

View File

@@ -2,25 +2,28 @@ import { useState, useEffect } from "react";
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
import { Settings, Puzzle, Settings2, Info, Server } from "lucide-react";
import { useTranslation } from "react-i18next";
import { listen } from '@tauri-apps/api/event';
import { listen } from "@tauri-apps/api/event";
import SettingsPanel from "@/components/Settings/SettingsPanel";
import GeneralSettings from "@/components/Settings/GeneralSettings";
import AboutView from "@/components/Settings/AboutView";
import Cloud from "@/components/Cloud/Cloud.tsx";
import Footer from "@/components/Footer";
import { useTray } from "@/hooks/useTray";
const tabIndexMap: { [key: string]: number } = {
'general': 0,
'extensions': 1,
'connect': 2,
'advanced': 3,
'about': 4
general: 0,
extensions: 1,
connect: 2,
advanced: 3,
about: 4,
};
function SettingsPage() {
const { t } = useTranslation();
useTray();
const tabs = [
{ name: t("settings.tabs.general"), icon: Settings },
{ name: t("settings.tabs.extensions"), icon: Puzzle },
@@ -41,7 +44,7 @@ function SettingsPage() {
});
return () => {
unlisten.then(fn => fn());
unlisten.then((fn) => fn());
};
}, []);

View File

@@ -1,10 +1,10 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { listen, emit } from '@tauri-apps/api/event';
import { listen, emit } from "@tauri-apps/api/event";
import { AppEndpoint } from "@/utils/tauri"
import { AppEndpoint } from "@/utils/tauri";
const ENDPOINT_CHANGE_EVENT = 'endpoint-changed';
const ENDPOINT_CHANGE_EVENT = "endpoint-changed";
export interface IServer {
id: string;
@@ -19,7 +19,7 @@ export interface IServer {
available?: boolean;
health?: {
status: string;
},
};
assistantCount?: number;
}
@@ -28,23 +28,26 @@ export type IAppStore = {
setShowTooltip: (showTooltip: boolean) => void;
error: string;
setError: (message: any) => void,
setError: (message: any) => void;
ssoRequestID: string;
setSSORequestID: (ssoRequestID: string) => void,
setSSORequestID: (ssoRequestID: string) => void;
// ssoServerID: string;
// setSSOServerID: (ssoServerID: string) => void,
endpoint: AppEndpoint,
endpoint_http: string,
endpoint_websocket: string,
setEndpoint: (endpoint: AppEndpoint) => void,
endpoint: AppEndpoint;
endpoint_http: string;
endpoint_websocket: string;
setEndpoint: (endpoint: AppEndpoint) => void;
language: string;
setLanguage: (language: string) => void;
isPinned: boolean,
setIsPinned: (isPinned: boolean) => void,
isPinned: boolean;
setIsPinned: (isPinned: boolean) => void;
initializeListeners: () => void;
showCocoShortcuts: string[];
setShowCocoShortcuts: (showCocoShortcuts: string[]) => void;
};
export const useAppStore = create<IAppStore>()(
@@ -66,7 +69,7 @@ export const useAppStore = create<IAppStore>()(
const withoutProtocol = endpoint.split("//")[1];
const endpoint_websocket = endpoint?.includes('https')
const endpoint_websocket = endpoint?.includes("https")
? `wss://${withoutProtocol}/ws`
: `ws://${withoutProtocol}/ws`;
@@ -79,7 +82,7 @@ export const useAppStore = create<IAppStore>()(
await emit(ENDPOINT_CHANGE_EVENT, {
endpoint,
endpoint_http,
endpoint_websocket
endpoint_websocket,
});
},
language: "en",
@@ -92,6 +95,12 @@ export const useAppStore = create<IAppStore>()(
set({ endpoint, endpoint_http, endpoint_websocket });
});
},
showCocoShortcuts: [],
setShowCocoShortcuts: (showCocoShortcuts: string[]) => {
console.log("set showCocoShortcuts", showCocoShortcuts);
return set({ showCocoShortcuts });
},
}),
{
name: "app-store",