Skip to content

Commit

Permalink
feat: add open app base dir functionality (#420)
Browse files Browse the repository at this point in the history
  • Loading branch information
halajohn authored Dec 17, 2024
1 parent 577ca90 commit 59ca2ae
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 8 deletions.
40 changes: 40 additions & 0 deletions core/src/ten_manager/designer_frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
fetchDesignerVersion,
fetchGraphs,
fetchNodes,
setBaseDir,
} from "./api/api";
import { Graph } from "./api/interface";
import { CustomNodeType } from "./flow/CustomNode";
Expand All @@ -38,6 +39,8 @@ const App: React.FC = () => {
const [showGraphSelection, setShowGraphSelection] = useState<boolean>(false);
const [nodes, setNodes] = useState<CustomNodeType[]>([]);
const [edges, setEdges] = useState<CustomEdgeType[]>([]);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null);

const { theme, setTheme } = useTheme();

Expand Down Expand Up @@ -113,13 +116,26 @@ const App: React.FC = () => {
setEdges((eds) => applyEdgeChanges(changes, eds));
}, []);

const handleSetBaseDir = useCallback(async (folderPath: string) => {
try {
await setBaseDir(folderPath);
setSuccessMessage("Successfully opened a new app folder.");
setNodes([]); // Clear the contents of the FlowCanvas.
setEdges([]);
} catch (error) {
setErrorMessage("Failed to open a new app folder.");
console.error(error);
}
}, []);

return (
<div className={`theme-${theme}`}>
<AppBar
version={version}
onOpenSettings={() => setShowSettings(true)}
onAutoLayout={performAutoLayout}
onOpenExistingGraph={handleOpenExistingGraph}
onSetBaseDir={handleSetBaseDir}
/>
<FlowCanvas
nodes={nodes}
Expand Down Expand Up @@ -159,6 +175,30 @@ const App: React.FC = () => {
</ul>
</Popup>
)}
{successMessage && (
<Popup
title="Ok"
onClose={() => setSuccessMessage(null)}
resizable={false}
initialWidth={300}
initialHeight={150}
onCollapseToggle={() => {}}
>
<p>{successMessage}</p>
</Popup>
)}
{errorMessage && (
<Popup
title="Error"
onClose={() => setErrorMessage(null)}
resizable={false}
initialWidth={300}
initialHeight={150}
onCollapseToggle={() => {}}
>
<p>{errorMessage}</p>
</Popup>
)}
</div>
);
};
Expand Down
24 changes: 24 additions & 0 deletions core/src/ten_manager/designer_frontend/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
FileContentResponse,
SaveFileRequest,
SuccessResponse,
SetBaseDirResponse,
SetBaseDirRequest,
} from "./interface";

export interface ExtensionAddon {
Expand Down Expand Up @@ -165,3 +167,25 @@ export const saveFileContent = async (
throw new Error(`Failed to save file content: ${data.status}`);
}
};

export const setBaseDir = async (
baseDir: string
): Promise<ApiResponse<SetBaseDirResponse>> => {
const requestBody: SetBaseDirRequest = { base_dir: baseDir };

const response = await fetch("/api/designer/v1/base-dir", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
});

if (!response.ok) {
throw new Error(`Failed to set base directory: ${response.status}`);
}

const data: ApiResponse<SetBaseDirResponse> = await response.json();

return data;
};
8 changes: 8 additions & 0 deletions core/src/ten_manager/designer_frontend/src/api/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,11 @@ export interface Graph {
name: string;
auto_start: boolean;
}

export interface SetBaseDirRequest {
base_dir: string;
}

export interface SetBaseDirResponse {
success: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface AppBarProps {
onOpenExistingGraph: () => void;
onAutoLayout: () => void;
onOpenSettings: () => void;
onSetBaseDir: (folderPath: string) => void;
}

type MenuType = "file" | "edit" | "help" | null;
Expand All @@ -28,6 +29,7 @@ const AppBar: React.FC<AppBarProps> = ({
onOpenExistingGraph,
onAutoLayout,
onOpenSettings,
onSetBaseDir,
}) => {
const [openMenu, setOpenMenu] = useState<MenuType>(null);
const appBarRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -91,6 +93,7 @@ const AppBar: React.FC<AppBarProps> = ({
onClick={() => handleOpenMenu("file")}
onHover={() => handleSwitchMenu("file")}
closeMenu={closeMenu}
onSetBaseDir={onSetBaseDir}
/>
<EditMenu
isOpen={openMenu === "edit"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,163 @@
// Licensed under the Apache License, Version 2.0, with certain conditions.
// Refer to the "LICENSE" file in the root directory for more information.
//
import React from "react";
import React, { useRef, useState } from "react";
import { FaFolderOpen } from "react-icons/fa";

import DropdownMenu, { DropdownMenuItem } from "./DropdownMenu";
import { setBaseDir } from "../../api/api";
import Popup from "../Popup/Popup";

interface FileMenuProps {
isOpen: boolean;
onClick: () => void;
onHover: () => void;
closeMenu: () => void;
onSetBaseDir: (folderPath: string) => void;
}

const FileMenu: React.FC<FileMenuProps> = ({
isOpen,
onClick,
onHover,
closeMenu,
onSetBaseDir,
}) => {
const fileInputRef = useRef<HTMLInputElement>(null);
const [isManualInput, setIsManualInput] = useState<boolean>(false);
const [manualPath, setManualPath] = useState<string>("");
const [showPopup, setShowPopup] = useState<boolean>(false);
const [popupMessage, setPopupMessage] = useState<string>("");

// Try to use `showDirectoryPicker` in the File System Access API.
const handleOpenFolder = async () => {
if ("showDirectoryPicker" in window) {
try {
const dirHandle = await (window as any).showDirectoryPicker();
const folderPath = dirHandle.name;
onSetBaseDir(folderPath);
} catch (error) {
console.error("Directory selection canceled or failed:", error);
}
} else {
// Fallback to input[type="file"].
if (fileInputRef.current) {
fileInputRef.current.click();
}
}
};

const handleFolderSelected = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files && files.length > 0) {
const firstFile = files[0];
const relativePath = firstFile.webkitRelativePath;
const folderPath = relativePath.split("/")[0];
onSetBaseDir(folderPath);
}
};

const handleManualSubmit = async () => {
if (!manualPath.trim()) {
setPopupMessage("The folder path cannot be empty.");
setShowPopup(true);
return;
}

try {
await setBaseDir(manualPath.trim());
setPopupMessage("Successfully opened a new app folder.");
onSetBaseDir(manualPath.trim());
setManualPath("");
} catch (error) {
setPopupMessage("Failed to open a new app folder.");
console.error(error);
} finally {
setShowPopup(true);
}
};

const items: DropdownMenuItem[] = [
{
label: "Open TEN app folder",
icon: <FaFolderOpen />,
onClick: () => {
handleOpenFolder();
closeMenu();
},
},
{
label: "Set App Folder Manually",
icon: <FaFolderOpen />,
onClick: () => {
setIsManualInput(true);
closeMenu();
},
},
];

return (
<DropdownMenu
title="File"
isOpen={isOpen}
onClick={onClick}
onHover={onHover}
items={items}
/>
<>
<DropdownMenu
title="File"
isOpen={isOpen}
onClick={onClick}
onHover={onHover}
items={items}
/>
{/* Hidden file input used for selecting folders. */}
<input
type="file"
ref={fileInputRef}
style={{ display: "none" }}
onChange={handleFolderSelected}
/>

{/* Popup for manually entering the folder path. */}
{isManualInput && (
<Popup
title="Set App Folder Manually"
onClose={() => setIsManualInput(false)}
resizable={false}
initialWidth={400}
initialHeight={200}
onCollapseToggle={() => {}}
>
<div>
<label htmlFor="folderPath">Folder Path:</label>
<input
type="text"
id="folderPath"
value={manualPath}
onChange={(e) => setManualPath(e.target.value)}
style={{ width: "100%", padding: "8px", marginTop: "5px" }}
placeholder="Enter folder path"
/>
<button
onClick={handleManualSubmit}
style={{ marginTop: "10px", padding: "8px 16px" }}
>
Submit
</button>
</div>
</Popup>
)}

{/* Popup to display success or error messages. */}
{showPopup && (
<Popup
title={popupMessage.includes("Ok") ? "Ok" : "Error"}
onClose={() => setShowPopup(false)}
resizable={false}
initialWidth={300}
initialHeight={150}
onCollapseToggle={() => {}}
>
<p>{popupMessage}</p>
</Popup>
)}
{/* >>>> END NEW */}
</>
);
};

Expand Down

0 comments on commit 59ca2ae

Please sign in to comment.