mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-01 18:28:38 +00: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 CommandPalette from '../command-palette/CommandPalette';
|
||||
import { useWebSocket } from '../../contexts/WebSocketContext';
|
||||
import { PaletteOpsProvider } from '../../contexts/PaletteOpsContext';
|
||||
import { useDeviceSettings } from '../../hooks/useDeviceSettings';
|
||||
import { useSessionProtection } from '../../hooks/useSessionProtection';
|
||||
import { useProjectsState } from '../../hooks/useProjectsState';
|
||||
@@ -146,6 +147,7 @@ export default function AppContent() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PaletteOpsProvider>
|
||||
<div className="fixed inset-0 flex bg-background" style={{ bottom: 'var(--keyboard-height, 0px)' }}>
|
||||
{!isMobile ? (
|
||||
<div className="h-full flex-shrink-0 border-r border-border/50">
|
||||
@@ -212,5 +214,6 @@ export default function AppContent() {
|
||||
onShowTab={setActiveTab}
|
||||
/>
|
||||
</div>
|
||||
</PaletteOpsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '../../shared/view/ui';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
import { usePaletteOps } from '../../contexts/PaletteOpsContext';
|
||||
import type { AppTab, Project } from '../../types/app';
|
||||
|
||||
import { GROUPS, parseMode } from './registry';
|
||||
@@ -35,6 +36,7 @@ export default function CommandPalette({
|
||||
const [search, setSearch] = React.useState('');
|
||||
const { toggleDarkMode } = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const ops = usePaletteOps();
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
@@ -60,8 +62,8 @@ export default function CommandPalette({
|
||||
}, []);
|
||||
|
||||
const openFile = React.useCallback((path: string) => {
|
||||
window.openFile?.(path);
|
||||
}, []);
|
||||
ops.openFile(path);
|
||||
}, [ops]);
|
||||
|
||||
const filter = React.useCallback(
|
||||
(value: string, rawSearch: string) => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import GitPanel from '../../git-panel/view/GitPanel';
|
||||
import PluginTabContent from '../../plugins/view/PluginTabContent';
|
||||
import type { MainContentProps } from '../types/types';
|
||||
import { useTaskMaster } from '../../../contexts/TaskMasterContext';
|
||||
import { usePaletteOpsRegister } from '../../../contexts/PaletteOpsContext';
|
||||
import { useTasksSettings } from '../../../contexts/TasksSettingsContext';
|
||||
import { useUiPreferences } from '../../../hooks/useUiPreferences';
|
||||
import { useEditorSidebar } from '../../code-editor/hooks/useEditorSidebar';
|
||||
@@ -91,19 +92,12 @@ function MainContent({
|
||||
}
|
||||
}, [shouldShowTasksTab, activeTab, setActiveTab]);
|
||||
|
||||
// Expose file-open to non-descendant features (command palette).
|
||||
useEffect(() => {
|
||||
const open = (filePath: string) => {
|
||||
usePaletteOpsRegister({
|
||||
openFile: (filePath: string) => {
|
||||
setActiveTab('files');
|
||||
handleFileOpen(filePath);
|
||||
};
|
||||
window.openFile = open;
|
||||
return () => {
|
||||
if (window.openFile === open) {
|
||||
delete window.openFile;
|
||||
}
|
||||
};
|
||||
}, [handleFileOpen, setActiveTab]);
|
||||
},
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
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;
|
||||
refreshProjects?: () => void | Promise<void>;
|
||||
openSettings?: (tab?: string) => void;
|
||||
openFile?: (filePath: string) => void;
|
||||
}
|
||||
|
||||
interface EventSourceEventMap {
|
||||
|
||||
Reference in New Issue
Block a user