mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-25 14:53:32 +08:00
feat(desktop): route macOS native About menu to in-app Settings page
Replace the default macOS about dialog with a custom menu that emits a Tauri event, navigating to the Settings > About tab within the React app. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bd604429ec
commit
301e0c3a33
@ -6,7 +6,10 @@ use std::{
|
|||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use tauri::{AppHandle, Manager, RunEvent, State};
|
use tauri::{
|
||||||
|
menu::{MenuBuilder, MenuItemBuilder, SubmenuBuilder},
|
||||||
|
AppHandle, Emitter, Manager, RunEvent, State,
|
||||||
|
};
|
||||||
use tauri_plugin_shell::{
|
use tauri_plugin_shell::{
|
||||||
process::{CommandChild, CommandEvent},
|
process::{CommandChild, CommandEvent},
|
||||||
ShellExt,
|
ShellExt,
|
||||||
@ -302,6 +305,63 @@ pub fn run() {
|
|||||||
get_server_url,
|
get_server_url,
|
||||||
restart_adapters_sidecar
|
restart_adapters_sidecar
|
||||||
])
|
])
|
||||||
|
.menu(|app| {
|
||||||
|
let about_item = MenuItemBuilder::with_id("nav_about", "About Claude Code Haha")
|
||||||
|
.build(app)?;
|
||||||
|
let settings_item = MenuItemBuilder::with_id("nav_settings", "Settings...")
|
||||||
|
.accelerator("CmdOrCtrl+,")
|
||||||
|
.build(app)?;
|
||||||
|
|
||||||
|
let app_submenu = SubmenuBuilder::new(app, "Claude Code Haha")
|
||||||
|
.item(&about_item)
|
||||||
|
.separator()
|
||||||
|
.item(&settings_item)
|
||||||
|
.separator()
|
||||||
|
.services()
|
||||||
|
.separator()
|
||||||
|
.hide()
|
||||||
|
.hide_others()
|
||||||
|
.show_all()
|
||||||
|
.separator()
|
||||||
|
.quit()
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let edit_submenu = SubmenuBuilder::new(app, "Edit")
|
||||||
|
.undo()
|
||||||
|
.redo()
|
||||||
|
.separator()
|
||||||
|
.cut()
|
||||||
|
.copy()
|
||||||
|
.paste()
|
||||||
|
.select_all()
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let view_submenu = SubmenuBuilder::new(app, "View")
|
||||||
|
.fullscreen()
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let window_submenu = SubmenuBuilder::new(app, "Window")
|
||||||
|
.minimize()
|
||||||
|
.maximize()
|
||||||
|
.close_window()
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
MenuBuilder::new(app)
|
||||||
|
.item(&app_submenu)
|
||||||
|
.item(&edit_submenu)
|
||||||
|
.item(&view_submenu)
|
||||||
|
.item(&window_submenu)
|
||||||
|
.build()
|
||||||
|
})
|
||||||
|
.on_menu_event(|app, event| match event.id().as_ref() {
|
||||||
|
"nav_about" => {
|
||||||
|
let _ = app.emit("native-menu-navigate", "about");
|
||||||
|
}
|
||||||
|
"nav_settings" => {
|
||||||
|
let _ = app.emit("native-menu-navigate", "settings");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let state = app.state::<ServerState>();
|
let state = app.state::<ServerState>();
|
||||||
let mut guard = state
|
let mut guard = state
|
||||||
|
|||||||
@ -4,10 +4,11 @@ import { ContentRouter } from './ContentRouter'
|
|||||||
import { ToastContainer } from '../shared/Toast'
|
import { ToastContainer } from '../shared/Toast'
|
||||||
import { UpdateChecker } from '../shared/UpdateChecker'
|
import { UpdateChecker } from '../shared/UpdateChecker'
|
||||||
import { useSettingsStore } from '../../stores/settingsStore'
|
import { useSettingsStore } from '../../stores/settingsStore'
|
||||||
|
import { useUIStore, type SettingsTab } from '../../stores/uiStore'
|
||||||
import { useKeyboardShortcuts } from '../../hooks/useKeyboardShortcuts'
|
import { useKeyboardShortcuts } from '../../hooks/useKeyboardShortcuts'
|
||||||
import { initializeDesktopServerUrl } from '../../lib/desktopRuntime'
|
import { initializeDesktopServerUrl } from '../../lib/desktopRuntime'
|
||||||
import { TabBar } from './TabBar'
|
import { TabBar } from './TabBar'
|
||||||
import { useTabStore } from '../../stores/tabStore'
|
import { useTabStore, SETTINGS_TAB_ID } from '../../stores/tabStore'
|
||||||
import { useChatStore } from '../../stores/chatStore'
|
import { useChatStore } from '../../stores/chatStore'
|
||||||
|
|
||||||
export function AppShell() {
|
export function AppShell() {
|
||||||
@ -46,6 +47,24 @@ export function AppShell() {
|
|||||||
}
|
}
|
||||||
}, [fetchSettings])
|
}, [fetchSettings])
|
||||||
|
|
||||||
|
// Listen for macOS native menu navigation events (About / Settings)
|
||||||
|
useEffect(() => {
|
||||||
|
let unlisten: (() => void) | undefined
|
||||||
|
import(/* @vite-ignore */ '@tauri-apps/api/event')
|
||||||
|
.then(({ listen }) =>
|
||||||
|
listen<string>('native-menu-navigate', (event) => {
|
||||||
|
const target = event.payload as SettingsTab | 'settings'
|
||||||
|
if (target === 'about') {
|
||||||
|
useUIStore.getState().setPendingSettingsTab('about')
|
||||||
|
}
|
||||||
|
useTabStore.getState().openTab(SETTINGS_TAB_ID, 'Settings', 'settings')
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then((fn) => { unlisten = fn })
|
||||||
|
.catch(() => {})
|
||||||
|
return () => { unlisten?.() }
|
||||||
|
}, [])
|
||||||
|
|
||||||
useKeyboardShortcuts()
|
useKeyboardShortcuts()
|
||||||
|
|
||||||
if (startupError) {
|
if (startupError) {
|
||||||
|
|||||||
@ -18,13 +18,19 @@ import { MarkdownRenderer } from '../components/markdown/MarkdownRenderer'
|
|||||||
import { useSkillStore } from '../stores/skillStore'
|
import { useSkillStore } from '../stores/skillStore'
|
||||||
import { SkillList } from '../components/skills/SkillList'
|
import { SkillList } from '../components/skills/SkillList'
|
||||||
import { SkillDetail } from '../components/skills/SkillDetail'
|
import { SkillDetail } from '../components/skills/SkillDetail'
|
||||||
|
import { useUIStore, type SettingsTab } from '../stores/uiStore'
|
||||||
type SettingsTab = 'providers' | 'permissions' | 'general' | 'adapters' | 'agents' | 'skills' | 'about'
|
|
||||||
|
|
||||||
export function Settings() {
|
export function Settings() {
|
||||||
const [activeTab, setActiveTab] = useState<SettingsTab>('providers')
|
const [activeTab, setActiveTab] = useState<SettingsTab>('providers')
|
||||||
|
const pendingSettingsTab = useUIStore((s) => s.pendingSettingsTab)
|
||||||
const t = useTranslation()
|
const t = useTranslation()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!pendingSettingsTab) return
|
||||||
|
setActiveTab(pendingSettingsTab)
|
||||||
|
useUIStore.getState().setPendingSettingsTab(null)
|
||||||
|
}, [pendingSettingsTab])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex flex-col overflow-hidden bg-[var(--color-surface)]">
|
<div className="flex-1 flex flex-col overflow-hidden bg-[var(--color-surface)]">
|
||||||
<div className="flex-1 flex overflow-hidden">
|
<div className="flex-1 flex overflow-hidden">
|
||||||
|
|||||||
@ -7,12 +7,15 @@ export type Toast = {
|
|||||||
duration?: number
|
duration?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SettingsTab = 'providers' | 'permissions' | 'general' | 'adapters' | 'agents' | 'skills' | 'about'
|
||||||
|
|
||||||
type ActiveView = 'code' | 'scheduled' | 'terminal' | 'history' | 'settings'
|
type ActiveView = 'code' | 'scheduled' | 'terminal' | 'history' | 'settings'
|
||||||
|
|
||||||
type UIStore = {
|
type UIStore = {
|
||||||
theme: 'light' | 'dark'
|
theme: 'light' | 'dark'
|
||||||
sidebarOpen: boolean
|
sidebarOpen: boolean
|
||||||
activeView: ActiveView
|
activeView: ActiveView
|
||||||
|
pendingSettingsTab: SettingsTab | null
|
||||||
activeModal: string | null
|
activeModal: string | null
|
||||||
toasts: Toast[]
|
toasts: Toast[]
|
||||||
|
|
||||||
@ -21,6 +24,7 @@ type UIStore = {
|
|||||||
toggleSidebar: () => void
|
toggleSidebar: () => void
|
||||||
setSidebarOpen: (open: boolean) => void
|
setSidebarOpen: (open: boolean) => void
|
||||||
setActiveView: (view: ActiveView) => void
|
setActiveView: (view: ActiveView) => void
|
||||||
|
setPendingSettingsTab: (tab: SettingsTab | null) => void
|
||||||
openModal: (id: string) => void
|
openModal: (id: string) => void
|
||||||
closeModal: () => void
|
closeModal: () => void
|
||||||
addToast: (toast: Omit<Toast, 'id'>) => void
|
addToast: (toast: Omit<Toast, 'id'>) => void
|
||||||
@ -33,6 +37,7 @@ export const useUIStore = create<UIStore>((set) => ({
|
|||||||
theme: 'light',
|
theme: 'light',
|
||||||
sidebarOpen: true,
|
sidebarOpen: true,
|
||||||
activeView: 'code',
|
activeView: 'code',
|
||||||
|
pendingSettingsTab: null,
|
||||||
activeModal: null,
|
activeModal: null,
|
||||||
toasts: [],
|
toasts: [],
|
||||||
|
|
||||||
@ -52,6 +57,7 @@ export const useUIStore = create<UIStore>((set) => ({
|
|||||||
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
|
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
|
||||||
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
||||||
setActiveView: (view) => set({ activeView: view }),
|
setActiveView: (view) => set({ activeView: view }),
|
||||||
|
setPendingSettingsTab: (tab) => set({ pendingSettingsTab: tab }),
|
||||||
openModal: (id) => set({ activeModal: id }),
|
openModal: (id) => set({ activeModal: id }),
|
||||||
closeModal: () => set({ activeModal: null }),
|
closeModal: () => set({ activeModal: null }),
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user