mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-02-15 05:07:35 +00:00
refactor(sidebar): extract typed app/sidebar architecture and split Sidebar into modular components
- Replace `src/App.jsx` with `src/App.tsx` and move route-level UI orchestration into `src/components/app/AppContent.tsx`. This separates provider/bootstrap concerns from runtime app layout logic, keeps route definitions minimal, and improves readability of the root app entry. - Introduce `src/hooks/useProjectsState.ts` to centralize project/session/sidebar state management previously embedded in `App.jsx`. This keeps the existing behavior for: project loading, Cursor session hydration, WebSocket `loading_progress` handling, additive-update protection for active sessions, URL-based session selection, sidebar refresh/delete/new-session flows. The hook now exposes a typed `sidebarSharedProps` contract and typed handlers used by `AppContent`. - Introduce `src/hooks/useSessionProtection.ts` for active/processing session lifecycle logic. This preserves session-protection behavior while isolating `activeSessions`, `processingSessions`, and temporary-session replacement into a dedicated reusable hook. - Replace monolithic `src/components/Sidebar.jsx` with typed `src/components/Sidebar.tsx` as a thin orchestrator. `Sidebar.tsx` now focuses on wiring controller state/actions, modal visibility, collapsed mode, and version modal behavior instead of rendering every UI branch inline. - Add `src/hooks/useSidebarController.ts` to encapsulate sidebar interaction/state logic. This includes expand/collapse state, inline project/session editing state, project starring/sorting/filtering, lazy session pagination, delete confirmations, rename/delete actions, refresh state, and mobile touch click handling. - Add strongly typed sidebar domain models in `src/components/sidebar/types.ts` and move sidebar-derived helpers into `src/components/sidebar/utils.ts`. Utility coverage now includes: session provider normalization, session view-model creation (name/time/activity/message count), project sorting/filtering, task indicator status derivation, starred-project persistence and readbacks. - Split sidebar rendering into focused components under `src/components/sidebar/`: `SidebarContent.tsx` for top-level sidebar layout composition. `SidebarProjectList.tsx` for project-state branching and project iteration. `SidebarProjectsState.tsx` for loading/empty/no-search-result placeholders. `SidebarProjectItem.tsx` for per-project desktop/mobile header rendering and actions. `SidebarProjectSessions.tsx` for expanded session area, skeletons, pagination, and new-session controls. `SidebarSessionItem.tsx` for per-session desktop/mobile item rendering and session actions. `SessionProviderIcon.tsx` for provider icon normalization. `SidebarHeader.tsx`, `SidebarFooter.tsx`, `SidebarCollapsed.tsx`, and `SidebarModals.tsx` as dedicated typed UI surfaces. This keeps rendering responsibilities local and significantly improves traceability. - Convert shared UI primitives from JSX to TSX: `src/components/ui/button.tsx`, `src/components/ui/input.tsx`, `src/components/ui/badge.tsx`, `src/components/ui/scroll-area.tsx`. These now provide typed props/variants (`forwardRef` where appropriate) while preserving existing class/behavior. - Add shared app typings in `src/types/app.ts` for projects/sessions/websocket/loading contracts used by new hooks/components. - Add global window declarations in `src/types/global.d.ts` for `__ROUTER_BASENAME__`, `refreshProjects`, and `openSettings`, removing implicit `any` usage for global integration points. - Update `src/main.jsx` to import `App.tsx` and keep app bootstrap consistent with the TS migration. - Update `src/components/QuickSettingsPanel.jsx` to self-resolve mobile state via `useDeviceSettings` (remove `isMobile` prop dependency), and update `src/components/ChatInterface.jsx` to render `QuickSettingsPanel` directly. This reduces prop drilling and keeps quick settings colocated with chat UI concerns.
This commit is contained in:
241
src/components/Sidebar.tsx
Normal file
241
src/components/Sidebar.tsx
Normal file
@@ -0,0 +1,241 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import VersionUpgradeModal from './modals/VersionUpgradeModal';
|
||||
import { useDeviceSettings } from '../hooks/useDeviceSettings';
|
||||
import { useVersionCheck } from '../hooks/useVersionCheck';
|
||||
import { useUiPreferences } from '../hooks/useUiPreferences';
|
||||
import { useSidebarController } from '../hooks/useSidebarController';
|
||||
import { useTaskMaster } from '../contexts/TaskMasterContext';
|
||||
import { useTasksSettings } from '../contexts/TasksSettingsContext';
|
||||
import SidebarCollapsed from './sidebar/SidebarCollapsed';
|
||||
import SidebarContent from './sidebar/SidebarContent';
|
||||
import SidebarModals from './sidebar/SidebarModals';
|
||||
import type { Project } from '../types/app';
|
||||
import type { SidebarProjectListProps } from './sidebar/SidebarProjectList';
|
||||
import type { MCPServerStatus, SidebarProps } from './sidebar/types';
|
||||
|
||||
type TaskMasterSidebarContext = {
|
||||
setCurrentProject: (project: Project) => void;
|
||||
mcpServerStatus: MCPServerStatus;
|
||||
};
|
||||
|
||||
function Sidebar({
|
||||
projects,
|
||||
selectedProject,
|
||||
selectedSession,
|
||||
onProjectSelect,
|
||||
onSessionSelect,
|
||||
onNewSession,
|
||||
onSessionDelete,
|
||||
onProjectDelete,
|
||||
isLoading,
|
||||
loadingProgress,
|
||||
onRefresh,
|
||||
onShowSettings,
|
||||
isMobile,
|
||||
}: SidebarProps) {
|
||||
const { t } = useTranslation(['sidebar', 'common']);
|
||||
const { isPWA } = useDeviceSettings({ trackMobile: false });
|
||||
const { updateAvailable, latestVersion, currentVersion, releaseInfo } = useVersionCheck(
|
||||
'siteboon',
|
||||
'claudecodeui',
|
||||
);
|
||||
const { preferences, setPreference } = useUiPreferences();
|
||||
const { sidebarVisible } = preferences;
|
||||
const { setCurrentProject, mcpServerStatus } = useTaskMaster() as TaskMasterSidebarContext;
|
||||
const { tasksEnabled } = useTasksSettings();
|
||||
|
||||
const {
|
||||
isSidebarCollapsed,
|
||||
expandedProjects,
|
||||
editingProject,
|
||||
showNewProject,
|
||||
editingName,
|
||||
loadingSessions,
|
||||
initialSessionsLoaded,
|
||||
currentTime,
|
||||
isRefreshing,
|
||||
editingSession,
|
||||
editingSessionName,
|
||||
searchFilter,
|
||||
deletingProjects,
|
||||
deleteConfirmation,
|
||||
sessionDeleteConfirmation,
|
||||
showVersionModal,
|
||||
filteredProjects,
|
||||
handleTouchClick,
|
||||
toggleProject,
|
||||
handleSessionClick,
|
||||
toggleStarProject,
|
||||
isProjectStarred,
|
||||
getProjectSessions,
|
||||
startEditing,
|
||||
cancelEditing,
|
||||
saveProjectName,
|
||||
showDeleteSessionConfirmation,
|
||||
confirmDeleteSession,
|
||||
requestProjectDelete,
|
||||
confirmDeleteProject,
|
||||
loadMoreSessions,
|
||||
handleProjectSelect,
|
||||
refreshProjects,
|
||||
updateSessionSummary,
|
||||
collapseSidebar: handleCollapseSidebar,
|
||||
expandSidebar: handleExpandSidebar,
|
||||
setShowNewProject,
|
||||
setEditingName,
|
||||
setEditingSession,
|
||||
setEditingSessionName,
|
||||
setSearchFilter,
|
||||
setDeleteConfirmation,
|
||||
setSessionDeleteConfirmation,
|
||||
setShowVersionModal,
|
||||
} = useSidebarController({
|
||||
projects,
|
||||
selectedProject,
|
||||
selectedSession,
|
||||
isLoading,
|
||||
isMobile,
|
||||
t,
|
||||
onRefresh,
|
||||
onProjectSelect,
|
||||
onSessionSelect,
|
||||
onSessionDelete,
|
||||
onProjectDelete,
|
||||
setCurrentProject,
|
||||
setSidebarVisible: (visible) => setPreference('sidebarVisible', visible),
|
||||
sidebarVisible,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
document.documentElement.classList.toggle('pwa-mode', isPWA);
|
||||
document.body.classList.toggle('pwa-mode', isPWA);
|
||||
}, [isPWA]);
|
||||
|
||||
const handleProjectCreated = () => {
|
||||
if (window.refreshProjects) {
|
||||
void window.refreshProjects();
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const projectListProps: SidebarProjectListProps = {
|
||||
projects,
|
||||
filteredProjects,
|
||||
selectedProject,
|
||||
selectedSession,
|
||||
isLoading,
|
||||
loadingProgress,
|
||||
expandedProjects,
|
||||
editingProject,
|
||||
editingName,
|
||||
loadingSessions,
|
||||
initialSessionsLoaded,
|
||||
currentTime,
|
||||
editingSession,
|
||||
editingSessionName,
|
||||
deletingProjects,
|
||||
tasksEnabled,
|
||||
mcpServerStatus,
|
||||
getProjectSessions,
|
||||
isProjectStarred,
|
||||
onEditingNameChange: setEditingName,
|
||||
onToggleProject: toggleProject,
|
||||
onProjectSelect: handleProjectSelect,
|
||||
onToggleStarProject: toggleStarProject,
|
||||
onStartEditingProject: startEditing,
|
||||
onCancelEditingProject: cancelEditing,
|
||||
onSaveProjectName: (projectName) => {
|
||||
void saveProjectName(projectName);
|
||||
},
|
||||
onDeleteProject: requestProjectDelete,
|
||||
onSessionSelect: handleSessionClick,
|
||||
onDeleteSession: showDeleteSessionConfirmation,
|
||||
onLoadMoreSessions: (project) => {
|
||||
void loadMoreSessions(project);
|
||||
},
|
||||
onNewSession,
|
||||
onEditingSessionNameChange: setEditingSessionName,
|
||||
onStartEditingSession: (sessionId, initialName) => {
|
||||
setEditingSession(sessionId);
|
||||
setEditingSessionName(initialName);
|
||||
},
|
||||
onCancelEditingSession: () => {
|
||||
setEditingSession(null);
|
||||
setEditingSessionName('');
|
||||
},
|
||||
onSaveEditingSession: (projectName, sessionId, summary) => {
|
||||
void updateSessionSummary(projectName, sessionId, summary);
|
||||
},
|
||||
touchHandlerFactory: handleTouchClick,
|
||||
t,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isSidebarCollapsed ? (
|
||||
<SidebarCollapsed
|
||||
onExpand={handleExpandSidebar}
|
||||
onShowSettings={onShowSettings}
|
||||
updateAvailable={updateAvailable}
|
||||
onShowVersionModal={() => setShowVersionModal(true)}
|
||||
t={t}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<SidebarModals
|
||||
showNewProject={showNewProject}
|
||||
onCloseNewProject={() => setShowNewProject(false)}
|
||||
onProjectCreated={handleProjectCreated}
|
||||
deleteConfirmation={deleteConfirmation}
|
||||
onCancelDeleteProject={() => setDeleteConfirmation(null)}
|
||||
onConfirmDeleteProject={confirmDeleteProject}
|
||||
sessionDeleteConfirmation={sessionDeleteConfirmation}
|
||||
onCancelDeleteSession={() => setSessionDeleteConfirmation(null)}
|
||||
onConfirmDeleteSession={confirmDeleteSession}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
<SidebarContent
|
||||
isPWA={isPWA}
|
||||
isMobile={isMobile}
|
||||
isLoading={isLoading}
|
||||
projects={projects}
|
||||
searchFilter={searchFilter}
|
||||
onSearchFilterChange={setSearchFilter}
|
||||
onClearSearchFilter={() => setSearchFilter('')}
|
||||
onRefresh={() => {
|
||||
void refreshProjects();
|
||||
}}
|
||||
isRefreshing={isRefreshing}
|
||||
onCreateProject={() => setShowNewProject(true)}
|
||||
onCollapseSidebar={handleCollapseSidebar}
|
||||
updateAvailable={updateAvailable}
|
||||
releaseInfo={releaseInfo}
|
||||
latestVersion={latestVersion}
|
||||
onShowVersionModal={() => setShowVersionModal(true)}
|
||||
onShowSettings={onShowSettings}
|
||||
projectListProps={projectListProps}
|
||||
t={t}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<VersionUpgradeModal
|
||||
isOpen={showVersionModal}
|
||||
onClose={() => setShowVersionModal(false)}
|
||||
releaseInfo={releaseInfo}
|
||||
currentVersion={currentVersion}
|
||||
latestVersion={latestVersion}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sidebar;
|
||||
Reference in New Issue
Block a user