Skip to content

Commit c77b91d

Browse files
authored
feat: default layout (#197)
1 parent 693c6d1 commit c77b91d

File tree

8 files changed

+235
-103
lines changed

8 files changed

+235
-103
lines changed

apps/array/src/main/preload.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,5 +478,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
478478
ipcRenderer.invoke("settings:get-worktree-location"),
479479
setWorktreeLocation: (location: string): Promise<void> =>
480480
ipcRenderer.invoke("settings:set-worktree-location", location),
481+
getTerminalLayout: (): Promise<"split" | "tabbed"> =>
482+
ipcRenderer.invoke("settings:get-terminal-layout"),
483+
setTerminalLayout: (mode: "split" | "tabbed"): Promise<void> =>
484+
ipcRenderer.invoke("settings:set-terminal-layout", mode),
481485
},
482486
});

apps/array/src/main/services/store.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,16 @@ ipcMain.handle(
165165
ipcMain.handle("renderer-store:remove", (_event, key: string): void => {
166166
rendererStore.delete(key);
167167
});
168+
169+
ipcMain.handle("settings:get-terminal-layout", (): string => {
170+
const encrypted = rendererStore.get("terminal-layout-mode");
171+
if (!encrypted) {
172+
return "split";
173+
}
174+
const decrypted = decrypt(encrypted as string);
175+
return decrypted || "split";
176+
});
177+
178+
ipcMain.handle("settings:set-terminal-layout", (_event, mode: string): void => {
179+
rendererStore.set("terminal-layout-mode", encrypt(mode));
180+
});

apps/array/src/renderer/components/StatusBar.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { StatusBarMenu } from "@components/StatusBarMenu";
2-
import { Badge, Box, Code, Flex, Kbd } from "@radix-ui/themes";
2+
import { GearIcon } from "@radix-ui/react-icons";
3+
import { Badge, Box, Code, Flex, IconButton, Kbd } from "@radix-ui/themes";
4+
import { useNavigationStore } from "@stores/navigationStore";
35
import { useStatusBarStore } from "@stores/statusBarStore";
46

57
import { IS_DEV } from "@/constants/environment";
@@ -10,6 +12,7 @@ interface StatusBarProps {
1012

1113
export function StatusBar({ showKeyHints = true }: StatusBarProps) {
1214
const { statusText, keyHints } = useStatusBarStore();
15+
const { toggleSettings } = useNavigationStore();
1316

1417
return (
1518
<Box className="flex flex-row items-center justify-between border-gray-6 border-t bg-gray-2 px-4 py-2">
@@ -42,15 +45,24 @@ export function StatusBar({ showKeyHints = true }: StatusBarProps) {
4245
</Flex>
4346
)}
4447

45-
{IS_DEV && (
46-
<Flex align="center" gap="2">
48+
<Flex align="center" gap="2">
49+
<IconButton
50+
size="1"
51+
variant="ghost"
52+
color="gray"
53+
onClick={toggleSettings}
54+
title="Settings"
55+
>
56+
<GearIcon />
57+
</IconButton>
58+
{IS_DEV && (
4759
<Badge size="1">
4860
<Code size="1" variant="ghost">
4961
DEV
5062
</Code>
5163
</Badge>
52-
</Flex>
53-
)}
64+
)}
65+
</Flex>
5466
</Box>
5567
);
5668
}

apps/array/src/renderer/features/panels/components/PanelLayout.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { DragDropProvider } from "@dnd-kit/react";
22
import type { Task } from "@shared/types";
3+
import { useSettingsStore } from "@stores/settingsStore";
34
import type React from "react";
45
import { useCallback, useEffect } from "react";
56
import { useDragDropHandlers } from "../hooks/useDragDropHandlers";
@@ -161,14 +162,25 @@ export const PanelLayout: React.FC<PanelLayoutProps> = ({ taskId, task }) => {
161162
const layout = usePanelLayoutStore((state) => state.getLayout(taskId));
162163
const initializeTask = usePanelLayoutStore((state) => state.initializeTask);
163164
const dragDropHandlers = useDragDropHandlers(taskId);
165+
const terminalLayoutMode = useSettingsStore(
166+
(state) => state.terminalLayoutMode,
167+
);
168+
const loadTerminalLayout = useSettingsStore(
169+
(state) => state.loadTerminalLayout,
170+
);
171+
const isLoading = useSettingsStore((state) => state.isLoading);
164172

165173
usePanelKeyboardShortcuts(taskId);
166174

167175
useEffect(() => {
168-
if (!layout) {
169-
initializeTask(taskId);
176+
loadTerminalLayout();
177+
}, [loadTerminalLayout]);
178+
179+
useEffect(() => {
180+
if (!layout && !isLoading) {
181+
initializeTask(taskId, terminalLayoutMode);
170182
}
171-
}, [taskId, layout, initializeTask]);
183+
}, [taskId, layout, initializeTask, terminalLayoutMode, isLoading]);
172184

173185
if (!layout) {
174186
return null;

apps/array/src/renderer/features/panels/store/panelLayoutStore.ts

Lines changed: 112 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ export interface PanelLayoutStore {
4444
taskLayouts: Record<string, TaskLayout>;
4545

4646
getLayout: (taskId: string) => TaskLayout | null;
47-
initializeTask: (taskId: string) => void;
47+
initializeTask: (
48+
taskId: string,
49+
terminalLayoutMode?: "split" | "tabbed",
50+
) => void;
4851
openFile: (taskId: string, filePath: string) => void;
4952
openArtifact: (taskId: string, fileName: string) => void;
5053
openDiff: (taskId: string, filePath: string, status?: string) => void;
@@ -96,109 +99,132 @@ export interface PanelLayoutStore {
9699
clearAllLayouts: () => void;
97100
}
98101

99-
function createDefaultPanelTree(): PanelNode {
102+
function createDefaultPanelTree(
103+
terminalLayoutMode: "split" | "tabbed" = "split",
104+
): PanelNode {
105+
const logsPanel: PanelNode = {
106+
type: "leaf",
107+
id: DEFAULT_PANEL_IDS.MAIN_PANEL,
108+
content: {
109+
id: DEFAULT_PANEL_IDS.MAIN_PANEL,
110+
tabs: [
111+
{
112+
id: DEFAULT_TAB_IDS.LOGS,
113+
label: "Logs",
114+
data: { type: "logs" },
115+
component: null,
116+
closeable: false,
117+
draggable: true,
118+
},
119+
],
120+
activeTabId: DEFAULT_TAB_IDS.LOGS,
121+
showTabs: true,
122+
droppable: true,
123+
},
124+
};
125+
126+
const terminalPanel: PanelNode = {
127+
type: "leaf",
128+
id: "terminal-panel",
129+
content: {
130+
id: "terminal-panel",
131+
tabs: [
132+
{
133+
id: DEFAULT_TAB_IDS.SHELL,
134+
label: "Terminal",
135+
data: {
136+
type: "terminal",
137+
terminalId: DEFAULT_TAB_IDS.SHELL,
138+
cwd: "",
139+
},
140+
component: null,
141+
closeable: true,
142+
draggable: true,
143+
},
144+
],
145+
activeTabId: DEFAULT_TAB_IDS.SHELL,
146+
showTabs: true,
147+
droppable: true,
148+
},
149+
};
150+
151+
const leftPanel: PanelNode =
152+
terminalLayoutMode === "split"
153+
? {
154+
type: "group",
155+
id: "left-group",
156+
direction: "vertical",
157+
sizes: [70, 30],
158+
children: [logsPanel, terminalPanel],
159+
}
160+
: {
161+
type: "leaf",
162+
id: DEFAULT_PANEL_IDS.MAIN_PANEL,
163+
content: {
164+
id: DEFAULT_PANEL_IDS.MAIN_PANEL,
165+
tabs: [
166+
{
167+
id: DEFAULT_TAB_IDS.LOGS,
168+
label: "Logs",
169+
data: { type: "logs" },
170+
component: null,
171+
closeable: false,
172+
draggable: true,
173+
},
174+
{
175+
id: DEFAULT_TAB_IDS.SHELL,
176+
label: "Terminal",
177+
data: {
178+
type: "terminal",
179+
terminalId: DEFAULT_TAB_IDS.SHELL,
180+
cwd: "",
181+
},
182+
component: null,
183+
closeable: true,
184+
draggable: true,
185+
},
186+
],
187+
activeTabId: DEFAULT_TAB_IDS.LOGS,
188+
showTabs: true,
189+
droppable: true,
190+
},
191+
};
192+
100193
return {
101194
type: "group",
102195
id: DEFAULT_PANEL_IDS.ROOT,
103196
direction: "horizontal",
104197
sizes: [...PANEL_SIZES.DEFAULT_SPLIT],
105198
children: [
199+
leftPanel,
106200
{
107201
type: "leaf",
108-
id: DEFAULT_PANEL_IDS.MAIN_PANEL,
202+
id: DEFAULT_PANEL_IDS.TOP_RIGHT,
109203
content: {
110-
id: DEFAULT_PANEL_IDS.MAIN_PANEL,
204+
id: DEFAULT_PANEL_IDS.TOP_RIGHT,
111205
tabs: [
112206
{
113-
id: DEFAULT_TAB_IDS.LOGS,
114-
label: "Logs",
115-
data: { type: "logs" },
207+
id: DEFAULT_TAB_IDS.CHANGES,
208+
label: "Changes",
209+
data: { type: "other" },
116210
component: null,
117211
closeable: false,
118-
draggable: true,
212+
draggable: false,
119213
},
120214
{
121-
id: DEFAULT_TAB_IDS.SHELL,
122-
label: "Terminal",
123-
data: {
124-
type: "terminal",
125-
terminalId: DEFAULT_TAB_IDS.SHELL,
126-
cwd: "",
127-
},
215+
id: DEFAULT_TAB_IDS.FILES,
216+
label: "Files",
217+
data: { type: "other" },
128218
component: null,
129-
closeable: true,
130-
draggable: true,
219+
closeable: false,
220+
draggable: false,
131221
},
132222
],
133-
activeTabId: DEFAULT_TAB_IDS.LOGS,
223+
activeTabId: DEFAULT_TAB_IDS.CHANGES,
134224
showTabs: true,
135-
droppable: true,
225+
droppable: false,
136226
},
137227
},
138-
{
139-
type: "group",
140-
id: DEFAULT_PANEL_IDS.RIGHT_GROUP,
141-
direction: "vertical",
142-
sizes: [...PANEL_SIZES.EVEN_SPLIT],
143-
children: [
144-
{
145-
type: "leaf",
146-
id: DEFAULT_PANEL_IDS.TOP_RIGHT,
147-
content: {
148-
id: DEFAULT_PANEL_IDS.TOP_RIGHT,
149-
tabs: [
150-
{
151-
id: DEFAULT_TAB_IDS.FILES,
152-
label: "Files",
153-
data: { type: "other" },
154-
component: null,
155-
closeable: false,
156-
draggable: false,
157-
},
158-
{
159-
id: DEFAULT_TAB_IDS.CHANGES,
160-
label: "Changes",
161-
data: { type: "other" },
162-
component: null,
163-
closeable: false,
164-
draggable: false,
165-
},
166-
],
167-
activeTabId: DEFAULT_TAB_IDS.FILES,
168-
showTabs: true,
169-
droppable: false,
170-
},
171-
},
172-
{
173-
type: "leaf",
174-
id: DEFAULT_PANEL_IDS.BOTTOM_RIGHT,
175-
content: {
176-
id: DEFAULT_PANEL_IDS.BOTTOM_RIGHT,
177-
tabs: [
178-
{
179-
id: DEFAULT_TAB_IDS.TODO_LIST,
180-
label: "Todo list",
181-
data: { type: "other" },
182-
component: null,
183-
closeable: false,
184-
draggable: false,
185-
},
186-
{
187-
id: DEFAULT_TAB_IDS.ARTIFACTS,
188-
label: "Artifacts",
189-
data: { type: "other" },
190-
component: null,
191-
closeable: false,
192-
draggable: false,
193-
},
194-
],
195-
activeTabId: DEFAULT_TAB_IDS.TODO_LIST,
196-
showTabs: true,
197-
droppable: false,
198-
},
199-
},
200-
],
201-
},
202228
],
203229
};
204230
}
@@ -254,12 +280,12 @@ export const usePanelLayoutStore = createWithEqualityFn<PanelLayoutStore>()(
254280
return get().taskLayouts[taskId] || null;
255281
},
256282

257-
initializeTask: (taskId) => {
283+
initializeTask: (taskId, terminalLayoutMode = "split") => {
258284
set((state) => ({
259285
taskLayouts: {
260286
...state.taskLayouts,
261287
[taskId]: {
262-
panelTree: createDefaultPanelTree(),
288+
panelTree: createDefaultPanelTree(terminalLayoutMode),
263289
openFiles: [],
264290
openArtifacts: [],
265291
draggingTabId: null,
@@ -740,7 +766,7 @@ export const usePanelLayoutStore = createWithEqualityFn<PanelLayoutStore>()(
740766
{
741767
name: "panel-layout-store",
742768
// Bump this version when the default panel structure changes to reset all layouts
743-
version: 5,
769+
version: 7,
744770
migrate: () => ({ taskLayouts: {} }),
745771
},
746772
),

0 commit comments

Comments
 (0)