Skip to content

Commit 74d6358

Browse files
committed
feat: display user apps in the workspaces table
1 parent 9d7630b commit 74d6358

File tree

1 file changed

+58
-11
lines changed

1 file changed

+58
-11
lines changed

site/src/pages/WorkspacesPage/WorkspacesTable.tsx

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Avatar } from "components/Avatar/Avatar";
2020
import { AvatarData } from "components/Avatar/AvatarData";
2121
import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton";
2222
import { Button } from "components/Button/Button";
23+
import { ExternalImage } from "components/ExternalImage/ExternalImage";
2324
import { VSCodeIcon } from "components/Icons/VSCodeIcon";
2425
import { VSCodeInsidersIcon } from "components/Icons/VSCodeInsidersIcon";
2526
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip";
@@ -63,6 +64,7 @@ import {
6364
getVSCodeHref,
6465
openAppInNewWindow,
6566
} from "modules/apps/apps";
67+
import { useAppLink } from "modules/apps/useAppLink";
6668
import { useDashboard } from "modules/dashboard/useDashboard";
6769
import { WorkspaceAppStatus } from "modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus";
6870
import { WorkspaceDormantBadge } from "modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge";
@@ -622,6 +624,9 @@ const PrimaryAction: FC<PrimaryActionProps> = ({
622624
);
623625
};
624626

627+
// The total number of apps that can be displayed in the workspace row
628+
const WORKSPACE_APPS_SLOTS = 4;
629+
625630
type WorkspaceAppsProps = {
626631
workspace: Workspace;
627632
};
@@ -647,11 +652,18 @@ const WorkspaceApps: FC<WorkspaceAppsProps> = ({ workspace }) => {
647652
return null;
648653
}
649654

655+
const builtinApps = new Set(agent.display_apps);
656+
builtinApps.delete("port_forwarding_helper");
657+
builtinApps.delete("ssh_helper");
658+
659+
const remainingSlots = WORKSPACE_APPS_SLOTS - builtinApps.size;
660+
const userApps = agent.apps.slice(0, remainingSlots);
661+
650662
const buttons: ReactNode[] = [];
651663

652-
if (agent.display_apps.includes("vscode")) {
664+
if (builtinApps.has("vscode")) {
653665
buttons.push(
654-
<AppLink
666+
<BaseIconLink
655667
key="vscode"
656668
isLoading={!token}
657669
label="Open VSCode"
@@ -664,13 +676,13 @@ const WorkspaceApps: FC<WorkspaceAppsProps> = ({ workspace }) => {
664676
})}
665677
>
666678
<VSCodeIcon />
667-
</AppLink>,
679+
</BaseIconLink>,
668680
);
669681
}
670682

671-
if (agent.display_apps.includes("vscode_insiders")) {
683+
if (builtinApps.has("vscode_insiders")) {
672684
buttons.push(
673-
<AppLink
685+
<BaseIconLink
674686
key="vscode-insiders"
675687
label="Open VSCode Insiders"
676688
isLoading={!token}
@@ -683,18 +695,29 @@ const WorkspaceApps: FC<WorkspaceAppsProps> = ({ workspace }) => {
683695
})}
684696
>
685697
<VSCodeInsidersIcon />
686-
</AppLink>,
698+
</BaseIconLink>,
687699
);
688700
}
689701

690-
if (agent.display_apps.includes("web_terminal")) {
702+
for (const app of userApps) {
703+
buttons.push(
704+
<IconAppLink
705+
key={app.id}
706+
app={app}
707+
workspace={workspace}
708+
agent={agent}
709+
/>,
710+
);
711+
}
712+
713+
if (builtinApps.has("web_terminal")) {
691714
const href = getTerminalHref({
692715
username: workspace.owner_name,
693716
workspace: workspace.name,
694717
agent: agent.name,
695718
});
696719
buttons.push(
697-
<AppLink
720+
<BaseIconLink
698721
key="terminal"
699722
href={href}
700723
onClick={(e) => {
@@ -704,21 +727,45 @@ const WorkspaceApps: FC<WorkspaceAppsProps> = ({ workspace }) => {
704727
label="Open Terminal"
705728
>
706729
<SquareTerminalIcon />
707-
</AppLink>,
730+
</BaseIconLink>,
708731
);
709732
}
710733

711734
return buttons;
712735
};
713736

714-
type AppLinkProps = PropsWithChildren<{
737+
type IconAppLinkProps = {
738+
app: WorkspaceApp;
739+
workspace: Workspace;
740+
agent: WorkspaceAgent;
741+
};
742+
743+
const IconAppLink: FC<IconAppLinkProps> = ({ app, workspace, agent }) => {
744+
const link = useAppLink(app, {
745+
workspace,
746+
agent,
747+
});
748+
749+
return (
750+
<BaseIconLink
751+
key={app.id}
752+
label={`Open ${link.label}`}
753+
href={link.href}
754+
onClick={link.onClick}
755+
>
756+
<ExternalImage src={app.icon} />
757+
</BaseIconLink>
758+
);
759+
};
760+
761+
type BaseIconLinkProps = PropsWithChildren<{
715762
label: string;
716763
href: string;
717764
isLoading?: boolean;
718765
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
719766
}>;
720767

721-
const AppLink: FC<AppLinkProps> = ({
768+
const BaseIconLink: FC<BaseIconLinkProps> = ({
722769
href,
723770
isLoading,
724771
label,

0 commit comments

Comments
 (0)