mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
feat: add CLI Viewer Page with multi-pane layout and state management
- Implemented the CliViewerPage component for displaying CLI outputs in a configurable multi-pane layout. - Integrated Zustand for state management, allowing for dynamic layout changes and tab management. - Added layout options: single, split horizontal, split vertical, and 2x2 grid. - Created viewerStore for managing layout, panes, and tabs, including actions for adding/removing panes and tabs. - Added CoordinatorPage barrel export for easier imports.
This commit is contained in:
@@ -3,10 +3,10 @@
|
||||
// ========================================
|
||||
// Convenient hook for locale management
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useAppStore, selectLocale } from '../stores/appStore';
|
||||
import type { Locale } from '../types/store';
|
||||
import { availableLocales } from '../lib/i18n';
|
||||
import { availableLocales, formatMessage } from '../lib/i18n';
|
||||
|
||||
export interface UseLocaleReturn {
|
||||
/** Current locale ('en' or 'zh') */
|
||||
@@ -51,3 +51,25 @@ export function useLocale(): UseLocaleReturn {
|
||||
availableLocales,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to format i18n messages with the current locale
|
||||
* @returns A formatMessage function for translating message IDs
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const formatMessage = useFormatMessage();
|
||||
* return <h1>{formatMessage('home.title')}</h1>;
|
||||
* ```
|
||||
*/
|
||||
export function useFormatMessage(): (
|
||||
id: string,
|
||||
values?: Record<string, string | number | boolean | Date | null | undefined>
|
||||
) => string {
|
||||
// Use useMemo to avoid recreating the function on each render
|
||||
return useMemo(() => {
|
||||
return (id: string, values?: Record<string, string | number | boolean | Date | null | undefined>) => {
|
||||
return formatMessage(id, values);
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ import { DEFAULT_DASHBOARD_LAYOUT } from '@/components/dashboard/defaultLayouts'
|
||||
const DEBOUNCE_DELAY = 1000; // 1 second debounce for layout saves
|
||||
const STORAGE_KEY = 'ccw-dashboard-layout';
|
||||
|
||||
// Version for layout schema - increment when widget IDs change
|
||||
const LAYOUT_VERSION = 2; // v2: workflow-task + recent-sessions
|
||||
|
||||
export interface UseUserDashboardLayoutResult {
|
||||
/** Current dashboard layouts */
|
||||
layouts: DashboardLayouts;
|
||||
@@ -59,8 +62,36 @@ export function useUserDashboardLayout(): UseUserDashboardLayoutResult {
|
||||
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const isSavingRef = useRef(false);
|
||||
|
||||
// Initialize layout if not set
|
||||
// Initialize layout if not set or version mismatch
|
||||
useEffect(() => {
|
||||
// Check if stored version matches current version
|
||||
const storedVersion = localStorage.getItem(`${STORAGE_KEY}-version`);
|
||||
const versionMismatch = storedVersion !== String(LAYOUT_VERSION);
|
||||
|
||||
if (versionMismatch) {
|
||||
// Version mismatch - reset to default and update version
|
||||
console.log(`Dashboard layout version changed (${storedVersion} -> ${LAYOUT_VERSION}), resetting to default`);
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
localStorage.setItem(`${STORAGE_KEY}-version`, String(LAYOUT_VERSION));
|
||||
|
||||
// Also clear dashboardLayout from Zustand persist storage
|
||||
try {
|
||||
const zustandStorage = localStorage.getItem('ccw-app-store');
|
||||
if (zustandStorage) {
|
||||
const parsed = JSON.parse(zustandStorage);
|
||||
if (parsed.state?.dashboardLayout) {
|
||||
delete parsed.state.dashboardLayout;
|
||||
localStorage.setItem('ccw-app-store', JSON.stringify(parsed));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to clear Zustand dashboard layout:', e);
|
||||
}
|
||||
|
||||
resetDashboardLayout();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dashboardLayout) {
|
||||
// Try to load from localStorage first
|
||||
try {
|
||||
@@ -96,9 +127,10 @@ export function useUserDashboardLayout(): UseUserDashboardLayoutResult {
|
||||
// Update Zustand store (which will persist to localStorage)
|
||||
setDashboardLayouts(newLayouts);
|
||||
|
||||
// Also save to additional localStorage backup
|
||||
// Also save to additional localStorage backup with version
|
||||
const currentWidgets = dashboardLayout?.widgets || DEFAULT_DASHBOARD_LAYOUT.widgets;
|
||||
setLocalStorageLayout({ layouts: newLayouts, widgets: currentWidgets });
|
||||
localStorage.setItem(`${STORAGE_KEY}-version`, String(LAYOUT_VERSION));
|
||||
|
||||
// TODO: When backend API is ready, uncomment this:
|
||||
// syncToBackend({ layouts: newLayouts, widgets: currentWidgets });
|
||||
@@ -114,9 +146,10 @@ export function useUserDashboardLayout(): UseUserDashboardLayoutResult {
|
||||
(newWidgets: WidgetConfig[]) => {
|
||||
setDashboardWidgets(newWidgets);
|
||||
|
||||
// Also save to localStorage backup
|
||||
// Also save to localStorage backup with version
|
||||
const currentLayouts = dashboardLayout?.layouts || DEFAULT_DASHBOARD_LAYOUT.layouts;
|
||||
setLocalStorageLayout({ layouts: currentLayouts, widgets: newWidgets });
|
||||
localStorage.setItem(`${STORAGE_KEY}-version`, String(LAYOUT_VERSION));
|
||||
|
||||
// TODO: When backend API is ready, uncomment this:
|
||||
// syncToBackend({ layouts: currentLayouts, widgets: newWidgets });
|
||||
@@ -134,8 +167,9 @@ export function useUserDashboardLayout(): UseUserDashboardLayoutResult {
|
||||
// Reset Zustand store
|
||||
resetDashboardLayout();
|
||||
|
||||
// Reset localStorage backup
|
||||
// Reset localStorage backup with version
|
||||
setLocalStorageLayout(DEFAULT_DASHBOARD_LAYOUT);
|
||||
localStorage.setItem(`${STORAGE_KEY}-version`, String(LAYOUT_VERSION));
|
||||
|
||||
// TODO: When backend API is ready, uncomment this:
|
||||
// syncToBackend(DEFAULT_DASHBOARD_LAYOUT);
|
||||
|
||||
Reference in New Issue
Block a user