mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-14 02:42:04 +08:00
feat: Implement phases 6 to 9 of the review cycle fix process, including discovery, batching, parallel planning, execution, and completion
- Added Phase 6: Fix Discovery & Batching with intelligent grouping and batching of findings. - Added Phase 7: Fix Parallel Planning to launch planning agents for concurrent analysis and aggregation of partial plans. - Added Phase 8: Fix Execution for stage-based execution of fixes with conservative test verification. - Added Phase 9: Fix Completion to aggregate results, generate summary reports, and handle session completion. - Introduced new frontend components: ResizeHandle for draggable resizing of sidebar panels and useResizablePanel hook for managing panel sizes with localStorage persistence. - Added PowerShell script for checking TypeScript errors in source code, excluding test files.
This commit is contained in:
136
ccw/frontend/src/pages/orchestrator/useResizablePanel.ts
Normal file
136
ccw/frontend/src/pages/orchestrator/useResizablePanel.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
// ========================================
|
||||
// useResizablePanel Hook
|
||||
// ========================================
|
||||
// Provides drag-to-resize functionality for sidebar panels.
|
||||
// Adapted from cc-wf-studio with Tailwind-friendly approach.
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
const DEFAULT_MIN_WIDTH = 200;
|
||||
const DEFAULT_MAX_WIDTH = 600;
|
||||
const DEFAULT_WIDTH = 300;
|
||||
const DEFAULT_STORAGE_KEY = 'ccw-orchestrator.panelWidth';
|
||||
|
||||
interface UseResizablePanelOptions {
|
||||
minWidth?: number;
|
||||
maxWidth?: number;
|
||||
defaultWidth?: number;
|
||||
storageKey?: string;
|
||||
/** Direction of drag relative to panel growth. 'left' means dragging left grows the panel (right-side panel). */
|
||||
direction?: 'left' | 'right';
|
||||
}
|
||||
|
||||
interface UseResizablePanelReturn {
|
||||
width: number;
|
||||
isResizing: boolean;
|
||||
handleMouseDown: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook for resizable panel functionality.
|
||||
*
|
||||
* Features:
|
||||
* - Drag-to-resize with mouse events
|
||||
* - Configurable min/max width constraints
|
||||
* - localStorage persistence
|
||||
* - Prevents text selection during drag
|
||||
*/
|
||||
export function useResizablePanel(options?: UseResizablePanelOptions): UseResizablePanelReturn {
|
||||
const minWidth = options?.minWidth ?? DEFAULT_MIN_WIDTH;
|
||||
const maxWidth = options?.maxWidth ?? DEFAULT_MAX_WIDTH;
|
||||
const defaultWidth = options?.defaultWidth ?? DEFAULT_WIDTH;
|
||||
const storageKey = options?.storageKey ?? DEFAULT_STORAGE_KEY;
|
||||
const direction = options?.direction ?? 'right';
|
||||
|
||||
// Initialize width from localStorage or use default
|
||||
const [width, setWidth] = useState<number>(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem(storageKey);
|
||||
if (saved) {
|
||||
const parsed = Number.parseInt(saved, 10);
|
||||
if (!Number.isNaN(parsed) && parsed >= minWidth && parsed <= maxWidth) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// localStorage unavailable
|
||||
}
|
||||
return defaultWidth;
|
||||
});
|
||||
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const startXRef = useRef<number>(0);
|
||||
const startWidthRef = useRef<number>(0);
|
||||
|
||||
// Handle mouse move during resize
|
||||
const handleMouseMove = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
const deltaX = e.clientX - startXRef.current;
|
||||
// For 'right' direction (left panel), dragging right grows the panel
|
||||
// For 'left' direction (right panel), dragging left grows the panel
|
||||
const newWidth = direction === 'right'
|
||||
? startWidthRef.current + deltaX
|
||||
: startWidthRef.current - deltaX;
|
||||
|
||||
const constrainedWidth = Math.max(minWidth, Math.min(maxWidth, newWidth));
|
||||
setWidth(constrainedWidth);
|
||||
},
|
||||
[minWidth, maxWidth, direction]
|
||||
);
|
||||
|
||||
// Handle mouse up to end resize
|
||||
const handleMouseUp = useCallback(() => {
|
||||
setIsResizing(false);
|
||||
}, []);
|
||||
|
||||
// Handle mouse down to start resize
|
||||
const handleMouseDown = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setIsResizing(true);
|
||||
startXRef.current = e.clientX;
|
||||
startWidthRef.current = width;
|
||||
},
|
||||
[width]
|
||||
);
|
||||
|
||||
// Set up global mouse event listeners
|
||||
useEffect(() => {
|
||||
if (isResizing) {
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
|
||||
// Prevent text selection during drag
|
||||
document.body.style.userSelect = 'none';
|
||||
document.body.style.cursor = 'ew-resize';
|
||||
} else {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
|
||||
document.body.style.userSelect = '';
|
||||
document.body.style.cursor = '';
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
document.body.style.userSelect = '';
|
||||
document.body.style.cursor = '';
|
||||
};
|
||||
}, [isResizing, handleMouseMove, handleMouseUp]);
|
||||
|
||||
// Persist width to localStorage whenever it changes
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(storageKey, width.toString());
|
||||
} catch {
|
||||
// localStorage unavailable
|
||||
}
|
||||
}, [width, storageKey]);
|
||||
|
||||
return {
|
||||
width,
|
||||
isResizing,
|
||||
handleMouseDown,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user