Compare commits

...

2 Commits

Author SHA1 Message Date
openhands
10a5c7e463 Convert tabs to use panel-based navigation instead of routes 2024-12-07 19:21:43 +00:00
openhands
729caafa6e Add App tab with iframe to localhost:4000 2024-12-07 19:18:51 +00:00
6 changed files with 90 additions and 27 deletions

View File

@@ -0,0 +1,11 @@
export function AppView() {
return (
<div className="h-full w-full">
<iframe
src="http://localhost:4000"
className="h-full w-full border-0"
title="App"
/>
</div>
);
}

View File

@@ -5,10 +5,12 @@ import { NavTab } from "./nav-tab";
interface ContainerProps {
label?: string;
labels?: {
id: string;
label: string;
to: string;
icon?: React.ReactNode;
isBeta?: boolean;
isActive: boolean;
onClick: (id: string) => void;
}[];
children: React.ReactNode;
className?: React.HTMLAttributes<HTMLDivElement>["className"];
@@ -29,8 +31,8 @@ export function Container({
>
{labels && (
<div className="flex text-xs h-[36px]">
{labels.map(({ label: l, to, icon, isBeta }) => (
<NavTab key={to} to={to} label={l} icon={icon} isBeta={isBeta} />
{labels.map(({ id, label: l, icon, isBeta, isActive, onClick }) => (
<NavTab key={id} id={id} label={l} icon={icon} isBeta={isBeta} isActive={isActive} onClick={onClick} />
))}
</div>
)}

View File

@@ -1,32 +1,29 @@
import { NavLink } from "react-router";
import { cn } from "#/utils/utils";
import { BetaBadge } from "./beta-badge";
interface NavTabProps {
to: string;
id: string;
label: string;
icon: React.ReactNode;
isBeta?: boolean;
isActive: boolean;
onClick: (id: string) => void;
}
export function NavTab({ to, label, icon, isBeta }: NavTabProps) {
export function NavTab({ id, label, icon, isBeta, isActive, onClick }: NavTabProps) {
return (
<NavLink
end
key={to}
to={to}
className={({ isActive }) =>
cn(
"px-2 border-b border-r border-neutral-600 bg-root-primary flex-1",
"first-of-type:rounded-tl-xl last-of-type:rounded-tr-xl last-of-type:border-r-0",
"flex items-center gap-2",
isActive && "bg-root-secondary",
)
}
<button
onClick={() => onClick(id)}
className={cn(
"px-2 border-b border-r border-neutral-600 bg-root-primary flex-1",
"first-of-type:rounded-tl-xl last-of-type:rounded-tr-xl last-of-type:border-r-0",
"flex items-center gap-2",
isActive && "bg-root-secondary",
)}
>
{icon}
{label}
{isBeta && <BetaBadge />}
</NavLink>
</button>
);
}

View File

@@ -0,0 +1,7 @@
import { AppView } from "#/components/features/app/app-view";
function App() {
return <AppView />;
}
export default App;

View File

@@ -1,6 +1,5 @@
import { useDisclosure } from "@nextui-org/react";
import React from "react";
import { Outlet } from "react-router";
import { useDispatch, useSelector } from "react-redux";
import { Controls } from "#/components/features/controls/controls";
import { RootState } from "#/store";
@@ -21,10 +20,16 @@ import { useUserPrefs } from "#/context/user-prefs-context";
import { useConversationConfig } from "#/hooks/query/use-conversation-config";
import { Container } from "#/components/layout/container";
import Security from "#/components/shared/modals/security/security";
import { useState } from "react";
import { AppView } from "#/components/features/app/app-view";
import { Workspace } from "#/components/features/workspace/workspace";
import { JupyterView } from "#/components/features/jupyter/jupyter-view";
import { BrowserView } from "#/components/features/browser/browser-view";
function App() {
const { token, gitHubToken } = useAuth();
const { settings } = useUserPrefs();
const [activeTab, setActiveTab] = useState("workspace");
const dispatch = useDispatch();
useConversationConfig();
@@ -59,6 +64,25 @@ function App() {
onOpenChange: onSecurityModalOpenChange,
} = useDisclosure();
const renderActiveTab = () => {
switch (activeTab) {
case "workspace":
return (
<FilesProvider>
<Workspace />
</FilesProvider>
);
case "jupyter":
return <JupyterView />;
case "browser":
return <BrowserView />;
case "app":
return <AppView />;
default:
return null;
}
};
return (
<WsClientProvider
enabled
@@ -78,19 +102,38 @@ function App() {
<Container
className="h-2/3"
labels={[
{ label: "Workspace", to: "", icon: <CodeIcon /> },
{ label: "Jupyter", to: "jupyter", icon: <ListIcon /> },
{
id: "workspace",
label: "Workspace",
icon: <CodeIcon />,
isActive: activeTab === "workspace",
onClick: setActiveTab
},
{
id: "jupyter",
label: "Jupyter",
icon: <ListIcon />,
isActive: activeTab === "jupyter",
onClick: setActiveTab
},
{
id: "browser",
label: "Browser",
to: "browser",
icon: <GlobeIcon />,
isBeta: true,
isActive: activeTab === "browser",
onClick: setActiveTab
},
{
id: "app",
label: "App",
icon: <GlobeIcon />,
isActive: activeTab === "app",
onClick: setActiveTab
},
]}
>
<FilesProvider>
<Outlet />
</FilesProvider>
{renderActiveTab()}
</Container>
{/* Terminal uses some API that is not compatible in a server-environment. For this reason, we lazy load it to ensure
* that it loads only in the client-side. */}

View File

@@ -3,19 +3,22 @@ enum TabOption {
CODE = "code",
BROWSER = "browser",
JUPYTER = "jupyter",
APP = "app",
}
type TabType =
| TabOption.PLANNER
| TabOption.CODE
| TabOption.BROWSER
| TabOption.JUPYTER;
| TabOption.JUPYTER
| TabOption.APP;
const AllTabs = [
TabOption.CODE,
TabOption.BROWSER,
TabOption.PLANNER,
TabOption.JUPYTER,
TabOption.APP,
];
export { AllTabs, TabOption, type TabType };