Skip to content

Commit 63b5f0b

Browse files
feat: add app iframe controls (#18421)
Add a home and "open in new tab" button. Other controls are not possible due to cross-origin restrictions. Closes #18178 --------- Co-authored-by: BrunoQuaresma <bruno_nonato_quaresma@hotmail.com>
1 parent b49e62f commit 63b5f0b

File tree

2 files changed

+93
-38
lines changed

2 files changed

+93
-38
lines changed

site/src/pages/TaskPage/TaskAppIframe.tsx

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import type { WorkspaceApp } from "api/typesGenerated";
2+
import { Button } from "components/Button/Button";
3+
import {
4+
DropdownMenu,
5+
DropdownMenuContent,
6+
DropdownMenuItem,
7+
DropdownMenuTrigger,
8+
} from "components/DropdownMenu/DropdownMenu";
9+
import { EllipsisVertical, ExternalLinkIcon, HouseIcon } from "lucide-react";
210
import { useAppLink } from "modules/apps/useAppLink";
311
import type { Task } from "modules/tasks/tasks";
4-
import type { FC } from "react";
12+
import { type FC, useRef } from "react";
13+
import { Link as RouterLink } from "react-router-dom";
514
import { cn } from "utils/cn";
615

716
type TaskAppIFrameProps = {
@@ -31,24 +40,69 @@ export const TaskAppIFrame: FC<TaskAppIFrameProps> = ({
3140
workspace: task.workspace,
3241
});
3342

34-
let href = link.href;
35-
try {
36-
const url = new URL(link.href);
37-
if (pathname) {
38-
url.pathname = pathname;
43+
const appHref = (): string => {
44+
try {
45+
const url = new URL(link.href, location.href);
46+
if (pathname) {
47+
url.pathname = pathname;
48+
}
49+
return url.toString();
50+
} catch (err) {
51+
console.warn(`Failed to parse URL ${link.href} for app ${app.id}`, err);
52+
return link.href;
3953
}
40-
href = url.toString();
41-
} catch (err) {
42-
console.warn(`Failed to parse URL ${link.href} for app ${app.id}`, err);
43-
}
54+
};
55+
56+
const frameRef = useRef<HTMLIFrameElement>(null);
57+
const frameSrc = appHref();
4458

4559
return (
46-
<iframe
47-
src={href}
48-
title={link.label}
49-
loading="eager"
50-
className={cn([active ? "block" : "hidden", "w-full h-full border-0"])}
51-
allow="clipboard-read; clipboard-write"
52-
/>
60+
<div className={cn([active ? "flex" : "hidden", "w-full h-full flex-col"])}>
61+
<div className="bg-surface-tertiary flex items-center p-2 py-1 gap-1">
62+
<Button
63+
size="icon"
64+
variant="subtle"
65+
onClick={(e) => {
66+
e.preventDefault();
67+
if (frameRef.current?.contentWindow) {
68+
frameRef.current.contentWindow.location.href = appHref();
69+
}
70+
}}
71+
>
72+
<HouseIcon />
73+
<span className="sr-only">Home</span>
74+
</Button>
75+
76+
{/* Possibly we will put a URL bar here, but for now we cannot due to
77+
* cross-origin restrictions in iframes. */}
78+
<div className="w-full"></div>
79+
80+
<DropdownMenu>
81+
<DropdownMenuTrigger asChild>
82+
<Button size="icon" variant="subtle" aria-label="More options">
83+
<EllipsisVertical aria-hidden="true" />
84+
<span className="sr-only">More options</span>
85+
</Button>
86+
</DropdownMenuTrigger>
87+
<DropdownMenuContent align="end">
88+
<DropdownMenuItem asChild>
89+
<RouterLink to={frameSrc} target="_blank">
90+
<ExternalLinkIcon />
91+
Open app in new tab
92+
</RouterLink>
93+
</DropdownMenuItem>
94+
</DropdownMenuContent>
95+
</DropdownMenu>
96+
</div>
97+
98+
<iframe
99+
ref={frameRef}
100+
src={frameSrc}
101+
title={link.label}
102+
loading="eager"
103+
className={"w-full h-full border-0"}
104+
allow="clipboard-read; clipboard-write"
105+
/>
106+
</div>
53107
);
54108
};

site/src/pages/TaskPage/TaskApps.tsx

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,21 @@ export const TaskApps: FC<TaskAppsProps> = ({ task }) => {
5757

5858
return (
5959
<main className="flex-1 flex flex-col">
60-
<div className="border-0 border-b border-border border-solid w-full p-1 flex gap-2">
61-
{embeddedApps.map((app) => (
62-
<TaskAppButton
63-
key={app.id}
64-
task={task}
65-
app={app}
66-
active={app.id === activeAppId}
67-
onClick={(e) => {
68-
e.preventDefault();
69-
setActiveAppId(app.id);
70-
}}
71-
/>
72-
))}
60+
<div className="w-full flex items-center border-0 border-b border-border border-solid">
61+
<div className="p-2 pb-0 flex gap-2 items-center">
62+
{embeddedApps.map((app) => (
63+
<TaskAppTab
64+
key={app.id}
65+
task={task}
66+
app={app}
67+
active={app.id === activeAppId}
68+
onClick={(e) => {
69+
e.preventDefault();
70+
setActiveAppId(app.id);
71+
}}
72+
/>
73+
))}
74+
</div>
7375

7476
{externalApps.length > 0 && (
7577
<div className="ml-auto">
@@ -122,19 +124,14 @@ export const TaskApps: FC<TaskAppsProps> = ({ task }) => {
122124
);
123125
};
124126

125-
type TaskAppButtonProps = {
127+
type TaskAppTabProps = {
126128
task: Task;
127129
app: WorkspaceApp;
128130
active: boolean;
129131
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => void;
130132
};
131133

132-
const TaskAppButton: FC<TaskAppButtonProps> = ({
133-
task,
134-
app,
135-
active,
136-
onClick,
137-
}) => {
134+
const TaskAppTab: FC<TaskAppTabProps> = ({ task, app, active, onClick }) => {
138135
const agent = task.workspace.latest_build.resources
139136
.flatMap((r) => r.agents)
140137
.filter((a) => !!a)
@@ -156,7 +153,11 @@ const TaskAppButton: FC<TaskAppButtonProps> = ({
156153
key={app.id}
157154
asChild
158155
className={cn([
159-
{ "text-content-primary": active },
156+
"px-3",
157+
{
158+
"text-content-primary bg-surface-tertiary rounded-sm rounded-b-none":
159+
active,
160+
},
160161
{ "opacity-75 hover:opacity-100": !active },
161162
])}
162163
>

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy