Files
kandev/apps/web/components/task/task-layout.tsx
T
2026-02-05 04:09:04 +00:00

218 lines
7.1 KiB
TypeScript

'use client';
import { memo, useMemo, useState, useCallback } from 'react';
import { Group, Panel, type Layout } from 'react-resizable-panels';
import { useDefaultLayout } from '@/lib/layout/use-default-layout';
import { useResponsiveBreakpoint } from '@/hooks/use-responsive-breakpoint';
import { useSessionLayoutState } from '@/hooks/use-session-layout-state';
import { TaskCenterPanel } from './task-center-panel';
import { TaskRightPanel } from './task-right-panel';
import { TaskFilesPanel } from './task-files-panel';
import { TaskSessionSidebar } from './task-session-sidebar';
import { SessionMobileLayout, SessionTabletLayout } from './mobile';
import { PreviewPanel } from '@/components/task/preview/preview-panel';
import { PreviewController } from '@/components/task/preview/preview-controller';
import { useLayoutStore } from '@/lib/state/layout-store';
import type { Repository, RepositoryScript } from '@/lib/types/http';
import type { Terminal } from '@/hooks/domains/session/use-terminals';
// Re-export for backwards compatibility
export type { SelectedDiff } from '@/hooks/use-session-layout-state';
const DEFAULT_HORIZONTAL_LAYOUT: Record<string, number> = {
left: 18,
chat: 57,
preview: 57,
right: 25,
};
const DEFAULT_PREVIEW_LAYOUT: Record<string, number> = {
chat: 60,
preview: 40,
};
const DEFAULT_LAYOUT_STATE = { left: true, chat: true, right: true, preview: false };
type TaskLayoutProps = {
workspaceId: string | null;
boardId: string | null;
sessionId?: string | null;
repository?: Repository | null;
initialScripts?: RepositoryScript[];
initialTerminals?: Terminal[];
defaultLayouts?: Record<string, Layout>;
taskTitle?: string;
baseBranch?: string;
worktreeBranch?: string | null;
};
export const TaskLayout = memo(function TaskLayout({
workspaceId,
boardId,
sessionId = null,
repository = null,
initialScripts = [],
initialTerminals,
defaultLayouts = {},
taskTitle,
baseBranch,
worktreeBranch,
}: TaskLayoutProps) {
const { isMobile, isTablet } = useResponsiveBreakpoint();
// Use shared layout state hook
const {
effectiveSessionId,
sessionKey,
selectedDiff,
handleSelectDiff,
handleClearSelectedDiff,
openFileRequest,
handleOpenFile,
handleFileOpenHandled,
} = useSessionLayoutState({ sessionId });
// Track active file path for highlighting in file tree
const [activeFilePath, setActiveFilePath] = useState<string | null>(null);
const handleActiveFileChange = useCallback((filePath: string | null) => {
setActiveFilePath(filePath);
}, []);
const layoutBySession = useLayoutStore((state) => state.columnsBySessionId);
const layoutState = useMemo(
() => layoutBySession[sessionKey] ?? DEFAULT_LAYOUT_STATE,
[layoutBySession, sessionKey]
);
const hasDevScript = Boolean(repository?.dev_script?.trim());
const sessionForPreview = effectiveSessionId;
const horizontalPanelIds = useMemo(() => {
const ids: string[] = [];
if (layoutState.left) ids.push('left');
ids.push(layoutState.preview ? 'preview' : 'chat');
if (layoutState.right) ids.push('right');
return ids;
}, [layoutState.left, layoutState.preview, layoutState.right]);
const horizontalLayoutKey = `task-layout-horizontal-v3:${horizontalPanelIds.join('|')}`;
const { defaultLayout: defaultHorizontalLayout, onLayoutChanged: onHorizontalLayoutChange } =
useDefaultLayout({
id: horizontalLayoutKey,
panelIds: horizontalPanelIds,
baseLayout: DEFAULT_HORIZONTAL_LAYOUT,
serverDefaultLayout: defaultLayouts[horizontalLayoutKey],
});
const previewPanelIds = ['chat', 'preview'];
const previewLayoutKey = 'task-layout-preview-v2';
const { defaultLayout: defaultPreviewLayout, onLayoutChanged: onPreviewLayoutChange } =
useDefaultLayout({
id: previewLayoutKey,
panelIds: previewPanelIds,
baseLayout: DEFAULT_PREVIEW_LAYOUT,
serverDefaultLayout: defaultLayouts[previewLayoutKey],
});
// Mobile layout
if (isMobile) {
return (
<SessionMobileLayout
workspaceId={workspaceId}
boardId={boardId}
sessionId={sessionId}
baseBranch={baseBranch}
worktreeBranch={worktreeBranch}
taskTitle={taskTitle}
/>
);
}
// Tablet layout
if (isTablet) {
return (
<SessionTabletLayout
workspaceId={workspaceId}
boardId={boardId}
sessionId={sessionId}
repository={repository}
defaultLayouts={defaultLayouts}
/>
);
}
// Desktop layout
const topFilesPanel = (
<TaskFilesPanel
onSelectDiff={handleSelectDiff}
onOpenFile={handleOpenFile}
activeFilePath={activeFilePath}
/>
);
return (
<div className="flex-1 min-h-0 px-4 pb-4">
<PreviewController sessionId={sessionForPreview} hasDevScript={hasDevScript} />
<Group
orientation="horizontal"
className="h-full min-h-0"
id={horizontalLayoutKey}
key={horizontalLayoutKey}
defaultLayout={defaultHorizontalLayout}
onLayoutChanged={onHorizontalLayoutChange}
>
{layoutState.left ? (
<>
<Panel id="left" minSize="180px" className="min-h-0">
<TaskSessionSidebar workspaceId={workspaceId} boardId={boardId} />
</Panel>
</>
) : null}
{layoutState.preview ? (
<Panel id="preview" className="min-h-0 min-w-0">
<Group
orientation="horizontal"
className="h-full min-h-0 min-w-0"
id={previewLayoutKey}
key={previewLayoutKey}
defaultLayout={defaultPreviewLayout}
onLayoutChanged={onPreviewLayoutChange}
>
<Panel id="chat" minSize="400px" className="min-h-0 min-w-0">
<TaskCenterPanel
selectedDiff={selectedDiff}
openFileRequest={openFileRequest}
onDiffHandled={handleClearSelectedDiff}
onFileOpenHandled={handleFileOpenHandled}
onActiveFileChange={handleActiveFileChange}
/>
</Panel>
<Panel id="preview" minSize="470px" className="min-h-0 min-w-0">
<PreviewPanel sessionId={sessionForPreview} hasDevScript={hasDevScript} />
</Panel>
</Group>
</Panel>
) : (
<Panel id="chat" minSize="400px" className="min-h-0 min-w-0">
<TaskCenterPanel
selectedDiff={selectedDiff}
openFileRequest={openFileRequest}
onDiffHandled={handleClearSelectedDiff}
onFileOpenHandled={handleFileOpenHandled}
onActiveFileChange={handleActiveFileChange}
sessionId={sessionId}
/>
</Panel>
)}
{layoutState.right ? (
<>
<Panel id="right" minSize="310px" className="min-h-0 min-w-0">
<TaskRightPanel topPanel={topFilesPanel} sessionId={sessionForPreview} repositoryId={repository?.id ?? null} initialScripts={initialScripts} initialTerminals={initialTerminals} />
</Panel>
</>
) : null}
</Group>
</div>
);
});