Skip to content

Commit 46a63da

Browse files
authored
ui feedback (#34)
1 parent 33516b8 commit 46a63da

File tree

8 files changed

+136
-45
lines changed

8 files changed

+136
-45
lines changed

src/main/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,19 @@ function createWindow(): void {
8080
const template: MenuItemConstructorOptions[] = [
8181
{
8282
label: "Array",
83-
submenu: [{ role: "about" }, { type: "separator" }, { role: "quit" }],
83+
submenu: [
84+
{ role: "about" },
85+
{ type: "separator" },
86+
{
87+
label: "Settings...",
88+
accelerator: "CmdOrCtrl+,",
89+
click: () => {
90+
mainWindow?.webContents.send("open-settings");
91+
},
92+
},
93+
{ type: "separator" },
94+
{ role: "quit" },
95+
],
8496
},
8597
{
8698
label: "Edit",

src/main/preload.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
6666
ipcRenderer.on(channel, wrapped);
6767
return () => ipcRenderer.removeListener(channel, wrapped);
6868
},
69+
onOpenSettings: (listener: () => void): (() => void) => {
70+
const wrapped = () => listener();
71+
ipcRenderer.on("open-settings", wrapped);
72+
return () => ipcRenderer.removeListener("open-settings", wrapped);
73+
},
6974
});

src/renderer/components/FolderPicker.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
import { Folder } from "@phosphor-icons/react";
1+
import { Folder, FolderIcon } from "@phosphor-icons/react";
22
import { ChevronDownIcon } from "@radix-ui/react-icons";
3-
import { Box, Button, Flex, Popover, Text, TextField } from "@radix-ui/themes";
3+
import {
4+
Box,
5+
Button,
6+
Flex,
7+
IconButton,
8+
Popover,
9+
Text,
10+
TextField,
11+
} from "@radix-ui/themes";
412
import { useEffect, useRef, useState } from "react";
513
import { useHotkeys } from "react-hotkeys-hook";
614
import { useFolderPickerStore } from "../stores/folderPickerStore";
@@ -143,6 +151,13 @@ export function FolderPicker({
143151
}
144152
};
145153

154+
const handleOpenNativeFileFinder = async () => {
155+
const selectedPath = await window.electronAPI.selectDirectory();
156+
if (selectedPath) {
157+
handleSelect(selectedPath);
158+
}
159+
};
160+
146161
const renderItem = (path: string, itemIndex: number) => (
147162
<Command.Item
148163
key={path}
@@ -207,8 +222,15 @@ export function FolderPicker({
207222
onChange={handleSearchChange}
208223
size={size}
209224
>
210-
<TextField.Slot>
211-
<Folder size={16} weight="regular" />
225+
<TextField.Slot side="right" style={{ paddingRight: 0 }}>
226+
<IconButton
227+
size="1"
228+
onClick={handleOpenNativeFileFinder}
229+
type="button"
230+
style={{ cursor: "pointer" }}
231+
>
232+
<FolderIcon size={12} weight="fill" />
233+
</IconButton>
212234
</TextField.Slot>
213235
</TextField.Root>
214236
</Box>

src/renderer/components/MainLayout.tsx

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Box, Flex } from "@radix-ui/themes";
22
import type { Task } from "@shared/types";
3-
import { useState } from "react";
3+
import { useCallback, useEffect, useState } from "react";
44
import { useHotkeys } from "react-hotkeys-hook";
55
import { useIntegrations } from "../hooks/useIntegrations";
66
import { useTabStore } from "../stores/tabStore";
@@ -15,12 +15,30 @@ import { WorkflowForm } from "./WorkflowForm";
1515
import { WorkflowView } from "./WorkflowView";
1616

1717
export function MainLayout() {
18-
const { activeTabId, tabs, createTab, setActiveTab } = useTabStore();
18+
const { activeTabId, tabs, createTab, setActiveTab, closeTab } =
19+
useTabStore();
1920
useIntegrations();
2021
const [commandMenuOpen, setCommandMenuOpen] = useState(false);
2122
const [taskCreateOpen, setTaskCreateOpen] = useState(false);
2223
const [workflowCreateOpen, setWorkflowCreateOpen] = useState(false);
2324

25+
const handleOpenSettings = useCallback(() => {
26+
const existingTab = tabs.find((tab) => tab.type === "settings");
27+
28+
if (existingTab) {
29+
if (activeTabId === existingTab.id) {
30+
closeTab(existingTab.id);
31+
} else {
32+
setActiveTab(existingTab.id);
33+
}
34+
} else {
35+
createTab({
36+
type: "settings",
37+
title: "Settings",
38+
});
39+
}
40+
}, [tabs, activeTabId, setActiveTab, createTab, closeTab]);
41+
2442
useHotkeys("mod+k", () => setCommandMenuOpen((prev) => !prev), {
2543
enabled: !commandMenuOpen,
2644
});
@@ -32,6 +50,17 @@ export function MainLayout() {
3250
});
3351
useHotkeys("mod+n", () => setTaskCreateOpen(true));
3452
useHotkeys("mod+shift+n", () => setWorkflowCreateOpen(true));
53+
useHotkeys("mod+,", () => handleOpenSettings());
54+
55+
useEffect(() => {
56+
const unsubscribe = window.electronAPI?.onOpenSettings(() => {
57+
handleOpenSettings();
58+
});
59+
60+
return () => {
61+
unsubscribe?.();
62+
};
63+
}, [handleOpenSettings]);
3564

3665
const handleSelectTask = (task: Task) => {
3766
const existingTab = tabs.find(
@@ -54,19 +83,6 @@ export function MainLayout() {
5483
}
5584
};
5685

57-
const handleOpenSettings = () => {
58-
const existingTab = tabs.find((tab) => tab.type === "settings");
59-
60-
if (existingTab) {
61-
setActiveTab(existingTab.id);
62-
} else {
63-
createTab({
64-
type: "settings",
65-
title: "Settings",
66-
});
67-
}
68-
};
69-
7086
const activeTab = tabs.find((tab) => tab.id === activeTabId);
7187

7288
return (

src/renderer/components/TaskCreate.tsx

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import {
88
Button,
99
Callout,
10+
Code,
1011
DataList,
1112
Dialog,
1213
Flex,
@@ -65,7 +66,15 @@ export function TaskCreate({ open, onOpenChange }: TaskCreateProps) {
6566
[workflows],
6667
);
6768

68-
const { register, handleSubmit, reset, control, setValue, watch } = useForm({
69+
const {
70+
register,
71+
handleSubmit,
72+
reset,
73+
control,
74+
setValue,
75+
watch,
76+
formState: { errors, isSubmitted },
77+
} = useForm({
6978
defaultValues: {
7079
title: "",
7180
description: "",
@@ -94,9 +103,7 @@ export function TaskCreate({ open, onOpenChange }: TaskCreateProps) {
94103
setValue("repository", repoKey);
95104

96105
if (!isRepoInIntegration(repoKey)) {
97-
setRepoWarning(
98-
`Repository ${repoKey} ${REPO_NOT_IN_INTEGRATION_WARNING}`,
99-
);
106+
setRepoWarning(repoKey);
100107
} else {
101108
setRepoWarning(null);
102109
}
@@ -161,6 +168,7 @@ export function TaskCreate({ open, onOpenChange }: TaskCreateProps) {
161168
data: newTask,
162169
});
163170
reset();
171+
setRepoWarning(null);
164172
if (!createMore) {
165173
onOpenChange(false);
166174
}
@@ -184,8 +192,15 @@ export function TaskCreate({ open, onOpenChange }: TaskCreateProps) {
184192
},
185193
);
186194

195+
const handleOpenChange = (newOpen: boolean) => {
196+
if (!newOpen) {
197+
setRepoWarning(null);
198+
}
199+
onOpenChange(newOpen);
200+
};
201+
187202
return (
188-
<Dialog.Root open={open} onOpenChange={onOpenChange}>
203+
<Dialog.Root open={open} onOpenChange={handleOpenChange}>
189204
<Dialog.Content
190205
maxHeight={isExpanded ? "90vh" : "600px"}
191206
height={isExpanded ? "90vh" : "auto"}
@@ -272,6 +287,10 @@ export function TaskCreate({ open, onOpenChange }: TaskCreateProps) {
272287
<Controller
273288
name="folderPath"
274289
control={control}
290+
rules={{
291+
required: true,
292+
validate: (v) => v.trim().length > 0,
293+
}}
275294
render={({ field }) => (
276295
<FolderPicker
277296
value={field.value}
@@ -348,11 +367,29 @@ export function TaskCreate({ open, onOpenChange }: TaskCreateProps) {
348367
</DataList.Root>
349368

350369
{repoWarning && (
351-
<Callout.Root color="orange" size="1">
352-
<Callout.Text>{repoWarning}</Callout.Text>
370+
<Callout.Root color="blue" size="1">
371+
<Callout.Text>
372+
<Code size="2">{repoWarning}</Code>{" "}
373+
{REPO_NOT_IN_INTEGRATION_WARNING}
374+
</Callout.Text>
353375
</Callout.Root>
354376
)}
355377

378+
{isSubmitted &&
379+
(errors.title ||
380+
errors.description ||
381+
errors.workflow ||
382+
errors.folderPath) && (
383+
<Callout.Root color="red" size="1">
384+
<Callout.Text>
385+
{errors.title && "Title is required. "}
386+
{errors.description && "Description is required. "}
387+
{errors.workflow && "Workflow is required. "}
388+
{errors.folderPath && "Working directory is required."}
389+
</Callout.Text>
390+
</Callout.Root>
391+
)}
392+
356393
{error && (
357394
<Callout.Root color="red" size="1">
358395
<Callout.Text>

src/renderer/components/TaskDetail.tsx

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
import {
2-
DiamondIcon,
3-
FilesIcon,
4-
GitBranchIcon,
5-
GithubLogoIcon,
6-
WarningCircleIcon,
7-
} from "@phosphor-icons/react";
1+
import { DiamondIcon, WarningCircleIcon } from "@phosphor-icons/react";
82
import { GearIcon, GlobeIcon } from "@radix-ui/react-icons";
93
import {
104
Box,
115
Button,
6+
Code,
127
DataList,
138
Flex,
149
Heading,
@@ -332,10 +327,15 @@ export function TaskDetail({ task: initialTask }: TaskDetailProps) {
332327
<DataList.Label>Repository</DataList.Label>
333328
<DataList.Value>
334329
<Flex align="center" gap="2">
335-
<Button size="1" variant="outline" color="gray">
336-
<GithubLogoIcon />
337-
{repositoryValue || "No repository connected"}
338-
</Button>
330+
{repositoryValue ? (
331+
<Code size="2" color="gray">
332+
{repositoryValue}
333+
</Code>
334+
) : (
335+
<Text size="2" color="gray">
336+
No repository connected
337+
</Text>
338+
)}
339339
{showRepoWarning && (
340340
<Tooltip content={REPO_NOT_IN_INTEGRATION_WARNING}>
341341
<WarningCircleIcon
@@ -353,10 +353,9 @@ export function TaskDetail({ task: initialTask }: TaskDetailProps) {
353353
<DataList.Label>Working Directory</DataList.Label>
354354
<DataList.Value>
355355
{repoPath ? (
356-
<Button size="1" variant="outline" color="gray">
357-
<FilesIcon />
356+
<Code size="2" color="gray">
358357
{displayRepoPath}
359-
</Button>
358+
</Code>
360359
) : (
361360
<Text size="2" color="gray">
362361
Not set
@@ -369,10 +368,9 @@ export function TaskDetail({ task: initialTask }: TaskDetailProps) {
369368
<DataList.Item>
370369
<DataList.Label>Branch</DataList.Label>
371370
<DataList.Value>
372-
<Button size="1" variant="outline" color="gray">
373-
<GitBranchIcon />
371+
<Code size="2" color="gray">
374372
{task.github_branch}
375-
</Button>
373+
</Code>
376374
</DataList.Value>
377375
</DataList.Item>
378376
)}

src/renderer/types/electron.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export interface IElectronAPI {
4242
channel: string,
4343
listener: (event: AgentEvent) => void,
4444
) => () => void;
45+
onOpenSettings: (listener: () => void) => () => void;
4546
}
4647

4748
declare global {

src/renderer/utils/repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ export const repoConfigToKey = (config?: RepositoryConfig): string => {
1919
};
2020

2121
export const REPO_NOT_IN_INTEGRATION_WARNING =
22-
"This repository is not available in your GitHub integration. We won't be able to create PRs when running tasks in the cloud.";
22+
"This repository is not connected to your GitHub integration. Tasks which run in the cloud won't be able to create PRs.";

0 commit comments

Comments
 (0)