agent-ecosystem/src/renderer/hooks/usePersistedGridLayout.ts
iliya 2317c948ff feat: enhance Kanban board with grid layout and persistence improvements
- Introduced a new KanbanGridLayout component for improved task organization and layout management.
- Updated KanbanBoard to utilize the new grid layout, enhancing the visual structure of tasks.
- Added CSS styles for grid layout and resizing handles to improve user interaction.
- Refactored KanbanColumn to support additional customization options for headers and body styles.
- Enhanced persistence flows to rely on repository abstractions, promoting better separation of concerns in storage management.
- Updated package dependencies to include react-grid-layout and react-resizable for enhanced layout capabilities.
2026-03-11 17:18:24 +02:00

107 lines
3.1 KiB
TypeScript

import { useCallback, useEffect, useMemo, useState } from 'react';
import {
createPersistedGridLayoutState,
mergeGridLayoutItems,
normalizePersistedGridLayoutState,
projectVisibleGridLayoutItems,
} from '@renderer/services/layout-system/gridLayoutSchema';
import type { GridLayoutRepository } from '@renderer/services/layout-system/GridLayoutRepository';
import type {
PersistedGridLayoutItem,
PersistedGridLayoutState,
} from '@renderer/services/layout-system/gridLayoutTypes';
interface UsePersistedGridLayoutOptions {
scopeKey: string;
allItemIds: string[];
visibleItemIds: string[];
cols: number;
repository: GridLayoutRepository<PersistedGridLayoutState>;
buildDefaultItems: (itemIds: string[]) => PersistedGridLayoutItem[];
}
interface UsePersistedGridLayoutResult {
allItems: PersistedGridLayoutItem[];
visibleItems: PersistedGridLayoutItem[];
isLoaded: boolean;
applyVisibleItems: (items: PersistedGridLayoutItem[], options?: { persist?: boolean }) => void;
}
export function usePersistedGridLayout({
scopeKey,
allItemIds,
visibleItemIds,
cols,
repository,
buildDefaultItems,
}: UsePersistedGridLayoutOptions): UsePersistedGridLayoutResult {
const defaultItems = useMemo(
() => buildDefaultItems(allItemIds),
[allItemIds, buildDefaultItems]
);
const [layoutState, setLayoutState] = useState<PersistedGridLayoutState>(() =>
normalizePersistedGridLayoutState(null, defaultItems)
);
const [loadedScopeKey, setLoadedScopeKey] = useState<string | null>(null);
const resolvedLayoutState = useMemo(
() => normalizePersistedGridLayoutState(layoutState, defaultItems),
[defaultItems, layoutState]
);
useEffect(() => {
let cancelled = false;
void repository
.load(scopeKey)
.then((stored) => {
if (cancelled) return;
setLayoutState(normalizePersistedGridLayoutState(stored, defaultItems));
setLoadedScopeKey(scopeKey);
})
.catch(() => {
if (cancelled) return;
setLayoutState(normalizePersistedGridLayoutState(null, defaultItems));
setLoadedScopeKey(scopeKey);
});
return () => {
cancelled = true;
};
}, [defaultItems, repository, scopeKey]);
const visibleItems = useMemo(
() => projectVisibleGridLayoutItems(resolvedLayoutState.items, visibleItemIds, cols),
[cols, resolvedLayoutState.items, visibleItemIds]
);
const applyVisibleItems = useCallback(
(items: PersistedGridLayoutItem[], options?: { persist?: boolean }) => {
setLayoutState((current) => {
const mergedItems = mergeGridLayoutItems(
normalizePersistedGridLayoutState(current, defaultItems).items,
items
);
const nextState = normalizePersistedGridLayoutState(
createPersistedGridLayoutState(mergedItems),
defaultItems
);
if (options?.persist) {
void repository.save(scopeKey, nextState);
}
return nextState;
});
},
[defaultItems, repository, scopeKey]
);
return {
allItems: resolvedLayoutState.items,
visibleItems,
isLoaded: loadedScopeKey === scopeKey,
applyVisibleItems,
};
}