Skip to content

Commit 26e10fb

Browse files
committed
feat: implement useOpenPath hook for consistent external link handling
- Introduced useOpenPath hook to streamline the process of opening external links across various components. - Replaced direct window.open calls with the new hook in multiple files, ensuring consistent behavior in both web and Tauri environments.
1 parent dfc71e3 commit 26e10fb

File tree

9 files changed

+73
-65
lines changed

9 files changed

+73
-65
lines changed

src-frontend/app/(desktop-only)/about/page.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { Icon } from "@/components/logo/icon";
44
import useHashParams from "@/hooks/use-hash-params";
5+
import { useOpenPath } from "@/hooks/use-open-path";
56
import { timeAgo } from "@/lib/utils";
67
import { useTheme } from "next-themes";
78
import { useEffect } from "react";
@@ -16,10 +17,7 @@ export default function MacbookInfo() {
1617
daemon_hash,
1718
daemon_build,
1819
} = useHashParams();
19-
const { openPath } =
20-
typeof window !== "undefined" && typeof window.__TAURI__ !== "undefined"
21-
? window.__TAURI__.opener
22-
: { openPath: (path: string) => window.open(path, "_blank") };
20+
const { openPath } = useOpenPath();
2321

2422
useEffect(() => {
2523
if (window.__TAURI__) {

src-frontend/app/(desktop-only)/updates/page.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Markdown from "react-markdown";
1616
import remarkGfm from "remark-gfm";
1717
import { BackgroundGradients } from "@/components/logo/background-gradients";
1818
import { IconGhost } from "@/components/logo/icon-ghost";
19+
import { useOpenPath } from "@/hooks/use-open-path";
1920

2021
enum Type {
2122
checking = "checking",
@@ -47,11 +48,7 @@ const initialState: UpdateWindowState = {
4748
export default function UpdatePage() {
4849
const [state, setState] = useState<UpdateWindowState>(initialState);
4950
const [animatedProgress, setAnimatedProgress] = useState(0);
50-
51-
const { openPath } =
52-
typeof window !== "undefined" && typeof window.__TAURI__ !== "undefined"
53-
? window.__TAURI__.opener
54-
: { openPath: (path: string) => window.open(path, "_blank") };
51+
const { openPath } = useOpenPath();
5552

5653
useEffect(() => {
5754
let unlisten: (() => void) | undefined;

src-frontend/components/apps/app-detail.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
import { useConnectionStore, useBreadcrumbStore } from "@/stores";
3737
import { AppFiles, AppInterface, AppLogs, AppStats } from "@/components/apps";
3838
import { toast } from "@/hooks/use-toast";
39+
import { useOpenPath } from "@/hooks/use-open-path";
3940
import { AppBreadcrumb } from "./apps-breadcrumb";
4041

4142
interface AppDetailProps {
@@ -58,11 +59,7 @@ export function AppDetail({ appId, onBack }: AppDetailProps) {
5859
settings: { url: daemonUrl },
5960
} = useConnectionStore();
6061
const { setBreadcrumb, clearBreadcrumb } = useBreadcrumbStore();
61-
62-
const { openPath } =
63-
typeof window !== "undefined" && typeof window.__TAURI__ !== "undefined"
64-
? window.__TAURI__.opener
65-
: { openPath: (path: string) => window.open(path, "_blank") };
62+
const { openPath } = useOpenPath();
6663

6764
const fetchApp = useCallback(async () => {
6865
setIsLoading(true);

src-frontend/components/diagnostic/ping-status-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export function PingStatusCard({
131131

132132
// Update ping history by adding a new value at the current position
133133
setPingHistory((prevHistory) => {
134-
const newHistory = [...prevHistory];
134+
const newHistory = prevHistory.map((item) => ({ ...item }));
135135

136136
// If we haven't filled the array yet, add the new ping at the current position
137137
if (dataCount < MAX_DATA_POINTS) {

src-frontend/components/marketplace/app-detail.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Toolbar } from "@/components/ui/toolbar";
1313
import { type MarketplaceApp, getMarketplaceApp } from "@/lib/api/marketplace";
1414
import { installApp, uninstallApp } from "@/lib/api/apps";
1515
import { toast } from "@/hooks/use-toast";
16+
import { useOpenPath } from "@/hooks/use-open-path";
1617
import { useBreadcrumbStore } from "@/stores";
1718
import { MarketplaceBreadcrumb } from "./marketplace-breadcrumb";
1819

@@ -73,6 +74,7 @@ export function AppDetail({ appId, onBack }: AppDetailProps) {
7374
const [isInstalled, setIsInstalled] = useState(false);
7475
const [reviewDialogOpen, setReviewDialogOpen] = useState(false);
7576
const { setBreadcrumb, clearBreadcrumb } = useBreadcrumbStore();
77+
const { openPath } = useOpenPath();
7678

7779
const handleInstall = useCallback(async () => {
7880
if (!app || !app.repository) {
@@ -356,24 +358,20 @@ export function AppDetail({ appId, onBack }: AppDetailProps) {
356358
<dt className="text-muted-foreground">Links</dt>
357359
<dd className="flex flex-wrap gap-2">
358360
{app.website && (
359-
<a
360-
href={app.website}
361-
target="_blank"
362-
rel="noopener noreferrer"
361+
<button
362+
onClick={() => openPath(app.website!)}
363363
className="flex items-center text-blue-600 hover:underline"
364364
>
365365
Website <ExternalLink className="ml-1 h-3 w-3" />
366-
</a>
366+
</button>
367367
)}
368368
{app.repository && (
369-
<a
370-
href={app.repository}
371-
target="_blank"
372-
rel="noopener noreferrer"
369+
<button
370+
onClick={() => openPath(app.repository!)}
373371
className="flex items-center text-blue-600 hover:underline"
374372
>
375373
Repository <ExternalLink className="ml-1 h-3 w-3" />
376-
</a>
374+
</button>
377375
)}
378376
</dd>
379377
</>

src-frontend/components/marketplace/install-confirmation-dialog.tsx

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { Button } from "@/components/ui/button";
2121
import { MarketplaceApp } from "@/lib/api/marketplace";
2222
import { installApp } from "@/lib/api/apps";
2323
import { toast } from "@/hooks/use-toast";
24+
import { useOpenPath } from "@/hooks/use-open-path";
2425

2526
interface InstallConfirmationDialogProps {
2627
isOpen: boolean;
@@ -38,6 +39,7 @@ export function InstallConfirmationDialog({
3839
onCancel,
3940
}: InstallConfirmationDialogProps) {
4041
const [isProcessing, setIsProcessing] = useState(false);
42+
const { openPath } = useOpenPath();
4143
const handleInstall = async () => {
4244
if (!app.repository) {
4345
toast({
@@ -102,23 +104,27 @@ export function InstallConfirmationDialog({
102104
</div>
103105
<AlertDialogDescription className="space-y-3 text-left md:ml-8">
104106
The app{" "}
105-
<a
106-
href={app.repository}
107-
target="_blank"
108-
rel="noopener noreferrer"
109-
className="m-0 inline-flex text-blue-600 hover:underline"
110-
>
111-
{app.name} <ExternalLink className="h-2 w-2 align-super" />
112-
</a>
107+
{app.repository ? (
108+
<button
109+
onClick={() => openPath(app.repository!)}
110+
className="m-0 inline-flex text-blue-600 hover:underline"
111+
>
112+
{app.name} <ExternalLink className="h-2 w-2 align-super" />
113+
</button>
114+
) : (
115+
<span className="font-medium">{app.name}</span>
116+
)}
113117
is added by{" "}
114-
<a
115-
href={app.website}
116-
target="_blank"
117-
rel="noopener noreferrer"
118-
className="m-0 inline-flex text-blue-600 hover:underline"
119-
>
120-
{app.author} <ExternalLink className="h-2 w-2 align-super" />
121-
</a>
118+
{app.website ? (
119+
<button
120+
onClick={() => openPath(app.website!)}
121+
className="m-0 inline-flex text-blue-600 hover:underline"
122+
>
123+
{app.author} <ExternalLink className="h-2 w-2 align-super" />
124+
</button>
125+
) : (
126+
<span className="font-medium">{app.author}</span>
127+
)}
122128
.
123129
<span className="my-3 flex items-center gap-2 text-sm">
124130
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-slate-200 p-1 dark:bg-slate-700">
@@ -136,14 +142,14 @@ export function InstallConfirmationDialog({
136142
</AlertDialogHeader>
137143
<AlertDialogFooter>
138144
<div className="flex w-full flex-col-reverse gap-2 sm:flex-row sm:justify-between md:ml-8">
139-
<Button
140-
variant="outline"
141-
onClick={() => {
142-
window.open(app.repository, "_blank");
143-
}}
144-
>
145-
View Source
146-
</Button>
145+
{app.repository && (
146+
<Button
147+
variant="outline"
148+
onClick={() => openPath(app.repository!)}
149+
>
150+
View Source
151+
</Button>
152+
)}
147153
<div className="flex flex-col-reverse gap-2 sm:flex-row">
148154
<AlertDialogCancel onClick={onCancel}>Cancel</AlertDialogCancel>
149155
<Button

src-frontend/components/profile/profile.tsx

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { Separator } from "@/components/ui/separator";
3636
import { Textarea } from "@/components/ui/textarea";
3737
import { ActivityHeatmap } from "./activity-heatmap";
3838
import { SocialConnectionButton } from "./social-connection-button";
39+
import { useOpenPath } from "@/hooks/use-open-path";
3940

4041
const profileFormSchema = z.object({
4142
displayName: z.string().min(2, {
@@ -92,6 +93,7 @@ const defaultValues: Partial<ProfileFormValues> = {
9293

9394
export default function Profile() {
9495
const [isEditing, setIsEditing] = useState(false);
96+
const { openPath } = useOpenPath();
9597

9698
const form = useForm<ProfileFormValues>({
9799
resolver: zodResolver(profileFormSchema),
@@ -392,50 +394,44 @@ export default function Profile() {
392394
<div className="flex items-center gap-2">
393395
<ExternalLink className="text-muted-foreground h-4 w-4" />
394396
<span className="text-sm font-medium">Website:</span>
395-
<a
396-
href={defaultValues.website}
397-
target="_blank"
398-
rel="noopener noreferrer"
397+
<button
398+
onClick={() => openPath(defaultValues.website!)}
399399
className="text-primary text-sm hover:underline"
400400
>
401401
{defaultValues.website.replace(/^https?:\/\//, "")}
402-
</a>
402+
</button>
403403
</div>
404404
)}
405405

406406
{defaultValues.xLink && (
407407
<div className="flex items-center gap-2">
408408
<Twitter className="text-muted-foreground h-4 w-4" />
409409
<span className="text-sm font-medium">X:</span>
410-
<a
411-
href={defaultValues.xLink}
412-
target="_blank"
413-
rel="noopener noreferrer"
410+
<button
411+
onClick={() => openPath(defaultValues.xLink!)}
414412
className="text-primary text-sm hover:underline"
415413
>
416414
{defaultValues.xLink.replace(
417415
/^https?:\/\/(www\.)?x\.com\//,
418416
"@",
419417
)}
420-
</a>
418+
</button>
421419
</div>
422420
)}
423421

424422
{defaultValues.linkedinLink && (
425423
<div className="flex items-center gap-2">
426424
<Linkedin className="text-muted-foreground h-4 w-4" />
427425
<span className="text-sm font-medium">LinkedIn:</span>
428-
<a
429-
href={defaultValues.linkedinLink}
430-
target="_blank"
431-
rel="noopener noreferrer"
426+
<button
427+
onClick={() => openPath(defaultValues.linkedinLink!)}
432428
className="text-primary text-sm hover:underline"
433429
>
434430
{defaultValues.linkedinLink.replace(
435431
/^https?:\/\/(www\.)?linkedin\.com\/in\//,
436432
"",
437433
)}
438-
</a>
434+
</button>
439435
</div>
440436
)}
441437
</div>

src-frontend/components/workspace/file-explorer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { FileSystemItem, ClipboardItem } from "@/lib/types";
1212
import { FileIcon } from "@/components/workspace/file-icon";
1313
import { SyncStatus } from "@/components/workspace/sync-status";
1414
import { PermissionsDialog } from "@/components/workspace/permissions-dialog";
15+
import { useOpenPath } from "@/hooks/use-open-path";
1516
import {
1617
ContextMenu,
1718
ContextMenuContent,
@@ -390,6 +391,7 @@ const FileExplorerItem = React.memo(function FileExplorerItem({
390391
isMobile,
391392
}: FileExplorerItemProps) {
392393
const { addFavorite } = useSidebarStore();
394+
const { openPath } = useOpenPath();
393395
const [platform, setPlatform] = useState<string>("macos");
394396
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
395397

@@ -407,7 +409,6 @@ const FileExplorerItem = React.memo(function FileExplorerItem({
407409

408410
try {
409411
if (typeof window !== "undefined" && window.__TAURI__) {
410-
const { openPath } = window.__TAURI__.opener;
411412
// Use platform-specific path separator
412413
const sep = platform === "windows" ? "\\" : "/";
413414

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useMemo } from "react";
2+
3+
export function useOpenPath() {
4+
const openPath = useMemo(() => {
5+
if (
6+
typeof window !== "undefined" &&
7+
typeof window.__TAURI__ !== "undefined"
8+
) {
9+
return window.__TAURI__.opener.openPath;
10+
}
11+
return (path: string) => window.open(path, "_blank");
12+
}, []);
13+
14+
return { openPath };
15+
}

0 commit comments

Comments
 (0)