mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-28 23:35:27 +08:00
refactor(command-palette): wire openFile through PaletteOpsContext
This commit is contained in:
@@ -6,6 +6,7 @@ import Sidebar from '../sidebar/view/Sidebar';
|
|||||||
import MainContent from '../main-content/view/MainContent';
|
import MainContent from '../main-content/view/MainContent';
|
||||||
import CommandPalette from '../command-palette/CommandPalette';
|
import CommandPalette from '../command-palette/CommandPalette';
|
||||||
import { useWebSocket } from '../../contexts/WebSocketContext';
|
import { useWebSocket } from '../../contexts/WebSocketContext';
|
||||||
|
import { PaletteOpsProvider } from '../../contexts/PaletteOpsContext';
|
||||||
import { useDeviceSettings } from '../../hooks/useDeviceSettings';
|
import { useDeviceSettings } from '../../hooks/useDeviceSettings';
|
||||||
import { useSessionProtection } from '../../hooks/useSessionProtection';
|
import { useSessionProtection } from '../../hooks/useSessionProtection';
|
||||||
import { useProjectsState } from '../../hooks/useProjectsState';
|
import { useProjectsState } from '../../hooks/useProjectsState';
|
||||||
@@ -146,6 +147,7 @@ export default function AppContent() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<PaletteOpsProvider>
|
||||||
<div className="fixed inset-0 flex bg-background" style={{ bottom: 'var(--keyboard-height, 0px)' }}>
|
<div className="fixed inset-0 flex bg-background" style={{ bottom: 'var(--keyboard-height, 0px)' }}>
|
||||||
{!isMobile ? (
|
{!isMobile ? (
|
||||||
<div className="h-full flex-shrink-0 border-r border-border/50">
|
<div className="h-full flex-shrink-0 border-r border-border/50">
|
||||||
@@ -212,5 +214,6 @@ export default function AppContent() {
|
|||||||
onShowTab={setActiveTab}
|
onShowTab={setActiveTab}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</PaletteOpsProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '../../shared/view/ui';
|
} from '../../shared/view/ui';
|
||||||
import { useTheme } from '../../contexts/ThemeContext';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
|
import { usePaletteOps } from '../../contexts/PaletteOpsContext';
|
||||||
import type { AppTab, Project } from '../../types/app';
|
import type { AppTab, Project } from '../../types/app';
|
||||||
|
|
||||||
import { GROUPS, parseMode } from './registry';
|
import { GROUPS, parseMode } from './registry';
|
||||||
@@ -35,6 +36,7 @@ export default function CommandPalette({
|
|||||||
const [search, setSearch] = React.useState('');
|
const [search, setSearch] = React.useState('');
|
||||||
const { toggleDarkMode } = useTheme();
|
const { toggleDarkMode } = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const ops = usePaletteOps();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
@@ -60,8 +62,8 @@ export default function CommandPalette({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const openFile = React.useCallback((path: string) => {
|
const openFile = React.useCallback((path: string) => {
|
||||||
window.openFile?.(path);
|
ops.openFile(path);
|
||||||
}, []);
|
}, [ops]);
|
||||||
|
|
||||||
const filter = React.useCallback(
|
const filter = React.useCallback(
|
||||||
(value: string, rawSearch: string) => {
|
(value: string, rawSearch: string) => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import GitPanel from '../../git-panel/view/GitPanel';
|
|||||||
import PluginTabContent from '../../plugins/view/PluginTabContent';
|
import PluginTabContent from '../../plugins/view/PluginTabContent';
|
||||||
import type { MainContentProps } from '../types/types';
|
import type { MainContentProps } from '../types/types';
|
||||||
import { useTaskMaster } from '../../../contexts/TaskMasterContext';
|
import { useTaskMaster } from '../../../contexts/TaskMasterContext';
|
||||||
|
import { usePaletteOpsRegister } from '../../../contexts/PaletteOpsContext';
|
||||||
import { useTasksSettings } from '../../../contexts/TasksSettingsContext';
|
import { useTasksSettings } from '../../../contexts/TasksSettingsContext';
|
||||||
import { useUiPreferences } from '../../../hooks/useUiPreferences';
|
import { useUiPreferences } from '../../../hooks/useUiPreferences';
|
||||||
import { useEditorSidebar } from '../../code-editor/hooks/useEditorSidebar';
|
import { useEditorSidebar } from '../../code-editor/hooks/useEditorSidebar';
|
||||||
@@ -91,19 +92,12 @@ function MainContent({
|
|||||||
}
|
}
|
||||||
}, [shouldShowTasksTab, activeTab, setActiveTab]);
|
}, [shouldShowTasksTab, activeTab, setActiveTab]);
|
||||||
|
|
||||||
// Expose file-open to non-descendant features (command palette).
|
usePaletteOpsRegister({
|
||||||
useEffect(() => {
|
openFile: (filePath: string) => {
|
||||||
const open = (filePath: string) => {
|
|
||||||
setActiveTab('files');
|
setActiveTab('files');
|
||||||
handleFileOpen(filePath);
|
handleFileOpen(filePath);
|
||||||
};
|
},
|
||||||
window.openFile = open;
|
});
|
||||||
return () => {
|
|
||||||
if (window.openFile === open) {
|
|
||||||
delete window.openFile;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [handleFileOpen, setActiveTab]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <MainContentStateView mode="loading" isMobile={isMobile} onMenuClick={onMenuClick} />;
|
return <MainContentStateView mode="loading" isMobile={isMobile} onMenuClick={onMenuClick} />;
|
||||||
|
|||||||
70
src/contexts/PaletteOpsContext.tsx
Normal file
70
src/contexts/PaletteOpsContext.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export type PaletteOps = {
|
||||||
|
openFile: (path: string) => void;
|
||||||
|
openSettings: (tab?: string) => void;
|
||||||
|
refreshProjects: () => Promise<void> | void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Handle = {
|
||||||
|
handlersRef: React.MutableRefObject<Partial<PaletteOps>>;
|
||||||
|
call: PaletteOps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PaletteOpsContext = createContext<Handle | null>(null);
|
||||||
|
|
||||||
|
const noop = () => undefined;
|
||||||
|
|
||||||
|
export function PaletteOpsProvider({ children }: { children: ReactNode }) {
|
||||||
|
const handlersRef = useRef<Partial<PaletteOps>>({});
|
||||||
|
|
||||||
|
const call = useMemo<PaletteOps>(
|
||||||
|
() => ({
|
||||||
|
openFile: (path) => handlersRef.current.openFile?.(path),
|
||||||
|
openSettings: (tab) => handlersRef.current.openSettings?.(tab),
|
||||||
|
refreshProjects: () => handlersRef.current.refreshProjects?.() ?? undefined,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const value = useMemo<Handle>(() => ({ handlersRef, call }), [call]);
|
||||||
|
|
||||||
|
return <PaletteOpsContext.Provider value={value}>{children}</PaletteOpsContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePaletteOps(): PaletteOps {
|
||||||
|
const handle = useContext(PaletteOpsContext);
|
||||||
|
if (!handle) {
|
||||||
|
return { openFile: noop, openSettings: noop, refreshProjects: noop };
|
||||||
|
}
|
||||||
|
return handle.call;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePaletteOpsRegister(partial: Partial<PaletteOps>) {
|
||||||
|
const handle = useContext(PaletteOpsContext);
|
||||||
|
const openFile = partial.openFile;
|
||||||
|
const openSettings = partial.openSettings;
|
||||||
|
const refreshProjects = partial.refreshProjects;
|
||||||
|
|
||||||
|
const installer = useCallback(() => {
|
||||||
|
if (!handle) return undefined;
|
||||||
|
const prev = { ...handle.handlersRef.current };
|
||||||
|
if (openFile) handle.handlersRef.current.openFile = openFile;
|
||||||
|
if (openSettings) handle.handlersRef.current.openSettings = openSettings;
|
||||||
|
if (refreshProjects) handle.handlersRef.current.refreshProjects = refreshProjects;
|
||||||
|
return () => {
|
||||||
|
if (openFile && handle.handlersRef.current.openFile === openFile) {
|
||||||
|
handle.handlersRef.current.openFile = prev.openFile;
|
||||||
|
}
|
||||||
|
if (openSettings && handle.handlersRef.current.openSettings === openSettings) {
|
||||||
|
handle.handlersRef.current.openSettings = prev.openSettings;
|
||||||
|
}
|
||||||
|
if (refreshProjects && handle.handlersRef.current.refreshProjects === refreshProjects) {
|
||||||
|
handle.handlersRef.current.refreshProjects = prev.refreshProjects;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [handle, openFile, openSettings, refreshProjects]);
|
||||||
|
|
||||||
|
useEffect(() => installer(), [installer]);
|
||||||
|
}
|
||||||
1
src/types/global.d.ts
vendored
1
src/types/global.d.ts
vendored
@@ -5,7 +5,6 @@ declare global {
|
|||||||
__ROUTER_BASENAME__?: string;
|
__ROUTER_BASENAME__?: string;
|
||||||
refreshProjects?: () => void | Promise<void>;
|
refreshProjects?: () => void | Promise<void>;
|
||||||
openSettings?: (tab?: string) => void;
|
openSettings?: (tab?: string) => void;
|
||||||
openFile?: (filePath: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EventSourceEventMap {
|
interface EventSourceEventMap {
|
||||||
|
|||||||
Reference in New Issue
Block a user