Skip to content

Commit 79933ea

Browse files
committed
Add a new open file tool in the changed file item toolbar, uses the existing external apps store
1 parent f9b316c commit 79933ea

File tree

1 file changed

+122
-30
lines changed

1 file changed

+122
-30
lines changed

apps/array/src/renderer/features/task-detail/components/ChangesPanel.tsx

Lines changed: 122 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
11
import { PanelMessage } from "@components/ui/PanelMessage";
22
import { isDiffTabActiveInTree, usePanelLayoutStore } from "@features/panels";
33
import { useTaskData } from "@features/task-detail/hooks/useTaskData";
4-
import { ArrowCounterClockwiseIcon, FileIcon } from "@phosphor-icons/react";
5-
import { Badge, Box, Flex, IconButton, Text, Tooltip } from "@radix-ui/themes";
4+
import {
5+
ArrowCounterClockwiseIcon,
6+
CodeIcon,
7+
CopyIcon,
8+
FileIcon,
9+
FilePlus,
10+
} from "@phosphor-icons/react";
11+
import {
12+
Badge,
13+
Box,
14+
DropdownMenu,
15+
Flex,
16+
IconButton,
17+
Text,
18+
Tooltip,
19+
} from "@radix-ui/themes";
620
import type { ChangedFile, GitFileStatus, Task } from "@shared/types";
21+
import { useExternalAppsStore } from "@stores/externalAppsStore";
722
import { useQuery, useQueryClient } from "@tanstack/react-query";
823
import { showMessageBox } from "@utils/dialog";
924
import { handleExternalAppAction } from "@utils/handleExternalAppAction";
25+
import { useState } from "react";
1026
import {
1127
selectWorktreePath,
1228
useWorkspaceStore,
@@ -92,7 +108,16 @@ function ChangedFileItem({
92108
(state) => state.closeDiffTabsForFile,
93109
);
94110
const queryClient = useQueryClient();
111+
const { detectedApps } = useExternalAppsStore();
112+
113+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
114+
const [isHovered, setIsHovered] = useState(false);
115+
116+
// show toolbar when hovered OR when dropdown is open
117+
const isToolbarVisible = isHovered || isDropdownOpen;
118+
95119
const fileName = file.path.split("/").pop() || file.path;
120+
const fullPath = `${repoPath}/${file.path}`;
96121
const indicator = getStatusIndicator(file.status);
97122

98123
const handleClick = () => {
@@ -101,14 +126,25 @@ function ChangedFileItem({
101126

102127
const handleContextMenu = async (e: React.MouseEvent) => {
103128
e.preventDefault();
104-
const fullPath = `${repoPath}/${file.path}`;
105129
const result = await window.electronAPI.showFileContextMenu(fullPath);
106130

107131
if (!result.action) return;
108132

109133
await handleExternalAppAction(result.action, fullPath, fileName);
110134
};
111135

136+
const handleOpenWith = async (appId: string) => {
137+
await handleExternalAppAction(
138+
{ type: "open-in-app", appId },
139+
fullPath,
140+
fileName,
141+
);
142+
};
143+
144+
const handleCopyPath = async () => {
145+
await handleExternalAppAction({ type: "copy-path" }, fullPath, fileName);
146+
};
147+
112148
const handleDiscard = async (e: React.MouseEvent) => {
113149
e.preventDefault();
114150

@@ -147,7 +183,13 @@ function ChangedFileItem({
147183
gap="1"
148184
onClick={handleClick}
149185
onContextMenu={handleContextMenu}
150-
className={`group ${isActive ? "border-accent-8 border-y bg-accent-4" : "border-transparent border-y hover:bg-gray-3"}`}
186+
onMouseEnter={() => setIsHovered(true)}
187+
onMouseLeave={() => setIsHovered(false)}
188+
className={
189+
isActive
190+
? "border-accent-8 border-y bg-accent-4"
191+
: "border-transparent border-y hover:bg-gray-3"
192+
}
151193
style={{
152194
cursor: "pointer",
153195
whiteSpace: "nowrap",
@@ -187,11 +229,10 @@ function ChangedFileItem({
187229
{file.originalPath ? `${file.originalPath}${file.path}` : file.path}
188230
</Text>
189231

190-
{hasLineStats && (
232+
{hasLineStats && !isToolbarVisible && (
191233
<Flex
192234
align="center"
193235
gap="1"
194-
className="group-hover:hidden"
195236
style={{ flexShrink: 0, fontSize: "10px", fontFamily: "monospace" }}
196237
>
197238
{(file.linesAdded ?? 0) > 0 && (
@@ -203,31 +244,82 @@ function ChangedFileItem({
203244
</Flex>
204245
)}
205246

206-
<Flex
207-
align="center"
208-
gap="1"
209-
className="hidden group-hover:flex"
210-
style={{ flexShrink: 0 }}
211-
>
212-
<Tooltip content="Discard changes">
213-
<IconButton
214-
size="1"
215-
variant="ghost"
216-
color="gray"
217-
onClick={handleDiscard}
218-
style={{
219-
flexShrink: 0,
220-
width: "18px",
221-
height: "18px",
222-
padding: 0,
223-
marginLeft: "2px",
224-
marginRight: "2px",
225-
}}
247+
{isToolbarVisible && (
248+
<Flex align="center" gap="1" style={{ flexShrink: 0 }}>
249+
<Tooltip content="Discard changes">
250+
<IconButton
251+
size="1"
252+
variant="ghost"
253+
color="gray"
254+
onClick={handleDiscard}
255+
style={{
256+
flexShrink: 0,
257+
width: "18px",
258+
height: "18px",
259+
padding: 0,
260+
marginLeft: "2px",
261+
marginRight: "2px",
262+
}}
263+
>
264+
<ArrowCounterClockwiseIcon size={12} />
265+
</IconButton>
266+
</Tooltip>
267+
268+
<DropdownMenu.Root
269+
open={isDropdownOpen}
270+
onOpenChange={setIsDropdownOpen}
226271
>
227-
<ArrowCounterClockwiseIcon size={12} />
228-
</IconButton>
229-
</Tooltip>
230-
</Flex>
272+
<Tooltip content="Open file">
273+
<DropdownMenu.Trigger>
274+
<IconButton
275+
size="1"
276+
variant="ghost"
277+
color="gray"
278+
onClick={(e) => e.stopPropagation()}
279+
style={{
280+
flexShrink: 0,
281+
width: "18px",
282+
height: "18px",
283+
padding: 0,
284+
}}
285+
>
286+
<FilePlus size={12} weight="regular" />
287+
</IconButton>
288+
</DropdownMenu.Trigger>
289+
</Tooltip>
290+
<DropdownMenu.Content size="1" align="end">
291+
{detectedApps.map((app) => (
292+
<DropdownMenu.Item
293+
key={app.id}
294+
onSelect={() => handleOpenWith(app.id)}
295+
>
296+
<Flex align="center" gap="2">
297+
{app.icon ? (
298+
<img
299+
src={app.icon}
300+
width={16}
301+
height={16}
302+
alt=""
303+
style={{ borderRadius: "2px" }}
304+
/>
305+
) : (
306+
<CodeIcon size={16} weight="regular" />
307+
)}
308+
<Text size="1">{app.name}</Text>
309+
</Flex>
310+
</DropdownMenu.Item>
311+
))}
312+
<DropdownMenu.Separator />
313+
<DropdownMenu.Item onSelect={handleCopyPath}>
314+
<Flex align="center" gap="2">
315+
<CopyIcon size={16} weight="regular" />
316+
<Text size="1">Copy Path</Text>
317+
</Flex>
318+
</DropdownMenu.Item>
319+
</DropdownMenu.Content>
320+
</DropdownMenu.Root>
321+
</Flex>
322+
)}
231323

232324
<Badge
233325
size="1"

0 commit comments

Comments
 (0)