Skip to content

Commit c8fb98c

Browse files
authored
refactor/panels and tasks (#124)
1 parent d1607ac commit c8fb98c

File tree

73 files changed

+1160
-908
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1160
-908
lines changed

src/renderer/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { MainLayout } from "@components/MainLayout";
22
import { DragDropProvider } from "@dnd-kit/react";
33
import { AuthScreen } from "@features/auth/components/AuthScreen";
44
import { useAuthStore } from "@features/auth/stores/authStore";
5+
import { useDragDropHandlers } from "@features/panels";
56
import { Flex, Spinner, Text } from "@radix-ui/themes";
67
import { initializePostHog } from "@renderer/lib/analytics";
78
import { useEffect, useState } from "react";

src/renderer/components/MainLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { TabBar } from "@components/TabBar";
44
import { UpdatePrompt } from "@components/UpdatePrompt";
55
import { CommandMenu } from "@features/command/components/CommandMenu";
66
import { SettingsView } from "@features/settings/components/SettingsView";
7-
import { TaskDetail } from "@features/tasks/components/TaskDetail";
8-
import { TaskList } from "@features/tasks/components/TaskList";
7+
import { TaskDetail } from "@features/task-detail/components/TaskDetail";
8+
import { TaskList } from "@features/task-list/components/TaskList";
99
import { useIntegrations } from "@hooks/useIntegrations";
1010
import { Box, Flex } from "@radix-ui/themes";
1111
import { track } from "@renderer/lib/analytics";

src/renderer/components/ui/panel/PanelLayout.tsx

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/renderer/components/ui/panel/DraggablePanel.tsx renamed to src/renderer/features/panels/components/DraggablePanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useDraggable, useDroppable } from "@dnd-kit/react";
22
import { Box, Flex, Text } from "@radix-ui/themes";
3-
import { usePanelStore } from "@stores/panelStore";
43
import type React from "react";
4+
import { usePanelStore } from "../store/panelStore";
55

66
interface DraggablePanelProps {
77
id: string;
File renamed without changes.

src/renderer/components/ui/panel/Panel.tsx renamed to src/renderer/features/panels/components/Panel.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66

77
type PanelProps = {
88
children: React.ReactNode;
9+
id?: string;
10+
order?: number;
911
className?: string;
1012
style?: React.CSSProperties;
1113
defaultSize?: number;
@@ -21,6 +23,8 @@ export const Panel = React.forwardRef<ImperativePanelHandle, PanelProps>(
2123
(
2224
{
2325
children,
26+
id,
27+
order,
2428
className,
2529
style,
2630
defaultSize,
@@ -36,6 +40,8 @@ export const Panel = React.forwardRef<ImperativePanelHandle, PanelProps>(
3640
return (
3741
<ResizablePanel
3842
ref={ref}
43+
id={id}
44+
order={order}
3945
className={className}
4046
style={style}
4147
defaultSize={defaultSize}

src/renderer/components/ui/panel/PanelDropZones.tsx renamed to src/renderer/features/panels/components/PanelDropZones.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useDroppable } from "@dnd-kit/react";
22
import { Box } from "@radix-ui/themes";
3-
import type { SplitDirection } from "@stores/panelStore";
43
import type React from "react";
4+
import type { SplitDirection } from "../store/panelStore";
55

66
type DropZoneType = SplitDirection | "center";
77

File renamed without changes.
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import React, { useEffect, useMemo, useRef, useState } from "react";
2+
import type { ImperativePanelGroupHandle } from "react-resizable-panels";
3+
import type { PanelNode } from "../store/panelStore";
4+
import { usePanelStore } from "../store/panelStore";
5+
import { Panel } from "./Panel";
6+
import { PanelGroup } from "./PanelGroup";
7+
import { PanelResizeHandle } from "./PanelResizeHandle";
8+
import { compilePanelTree } from "./PanelTree";
9+
import { TabbedPanel } from "./TabbedPanel";
10+
11+
interface PanelLayoutProps {
12+
tree: React.ReactElement;
13+
}
14+
15+
const PanelLayoutRenderer: React.FC<{ node: PanelNode }> = ({ node }) => {
16+
const [activeTabs, setActiveTabs] = useState<Record<string, string>>({});
17+
const updateSizes = usePanelStore((state) => state.updateSizes);
18+
const groupRefs = useRef<Map<string, ImperativePanelGroupHandle>>(new Map());
19+
20+
const handleSetActiveTab = (panelId: string, tabId: string) => {
21+
setActiveTabs((prev) => ({ ...prev, [panelId]: tabId }));
22+
};
23+
24+
const setGroupRef = (
25+
groupId: string,
26+
ref: ImperativePanelGroupHandle | null,
27+
) => {
28+
if (ref) {
29+
groupRefs.current.set(groupId, ref);
30+
} else {
31+
groupRefs.current.delete(groupId);
32+
}
33+
};
34+
35+
const renderNode = (currentNode: PanelNode): React.ReactNode => {
36+
if (currentNode.type === "leaf") {
37+
const activeTabId =
38+
activeTabs[currentNode.id] || currentNode.content.activeTabId;
39+
const content = {
40+
...currentNode.content,
41+
activeTabId,
42+
};
43+
44+
return (
45+
<TabbedPanel
46+
panelId={currentNode.id}
47+
content={content}
48+
onActiveTabChange={handleSetActiveTab}
49+
/>
50+
);
51+
}
52+
53+
if (currentNode.type === "group") {
54+
return (
55+
<PanelGroup
56+
ref={(ref) => setGroupRef(currentNode.id, ref)}
57+
direction={currentNode.direction}
58+
onLayout={(sizes) => {
59+
// Only update store, don't normalize here
60+
// The library is the source of truth for sizes during user interaction
61+
updateSizes(currentNode.id, sizes);
62+
}}
63+
>
64+
{currentNode.children.map((child, index) => (
65+
<React.Fragment key={child.id}>
66+
<Panel
67+
id={child.id}
68+
order={index}
69+
defaultSize={
70+
currentNode.sizes?.[index] ??
71+
100 / currentNode.children.length
72+
}
73+
minSize={15}
74+
>
75+
{renderNode(child)}
76+
</Panel>
77+
{index < currentNode.children.length - 1 && <PanelResizeHandle />}
78+
</React.Fragment>
79+
))}
80+
</PanelGroup>
81+
);
82+
}
83+
84+
return null;
85+
};
86+
87+
// Sync store changes to library when sizes change from operations like split/cleanup
88+
useEffect(() => {
89+
const syncSizesToLibrary = (currentNode: PanelNode) => {
90+
if (currentNode.type === "group" && currentNode.sizes) {
91+
const groupRef = groupRefs.current.get(currentNode.id);
92+
if (groupRef) {
93+
// Get current layout from library
94+
const currentLayout = groupRef.getLayout();
95+
96+
// Only update if sizes are significantly different (avoid feedback loops)
97+
const isDifferent = currentLayout.some(
98+
(size, i) => Math.abs(size - (currentNode.sizes?.[i] ?? 0)) > 0.1,
99+
);
100+
101+
if (
102+
isDifferent &&
103+
currentNode.sizes.length === currentLayout.length
104+
) {
105+
groupRef.setLayout(currentNode.sizes);
106+
}
107+
}
108+
109+
// Recursively sync child groups
110+
currentNode.children.forEach(syncSizesToLibrary);
111+
}
112+
};
113+
114+
syncSizesToLibrary(node);
115+
}, [node]);
116+
117+
return <>{renderNode(node)}</>;
118+
};
119+
120+
export const PanelLayout: React.FC<PanelLayoutProps> = ({ tree }) => {
121+
const compiledNode = useMemo(() => compilePanelTree(tree), [tree]);
122+
const setRoot = usePanelStore((state) => state.setRoot);
123+
const root = usePanelStore((state) => state.root);
124+
125+
useEffect(() => {
126+
// Only initialize if root is null (store not yet initialized)
127+
// Once initialized, store is the source of truth
128+
if (root === null) {
129+
setRoot(compiledNode);
130+
}
131+
}, [compiledNode, setRoot, root]);
132+
133+
return root ? <PanelLayoutRenderer node={root} /> : null;
134+
};

src/renderer/components/ui/panel/PanelResizeHandle.tsx renamed to src/renderer/features/panels/components/PanelResizeHandle.tsx

File renamed without changes.

0 commit comments

Comments
 (0)