[ALL-557] feat(frontend): Add save and discard actions to the editor (#4442)

Co-authored-by: mamoodi <mamoodiha@gmail.com>
This commit is contained in:
sp.wack
2024-10-17 21:14:55 +04:00
committed by GitHub
parent 154854bbe3
commit 6cb174b7d1
3 changed files with 112 additions and 6 deletions

View File

@@ -0,0 +1,62 @@
import { cn } from "@nextui-org/react";
import { HTMLAttributes } from "react";
interface EditorActionButtonProps {
onClick: () => void;
disabled: boolean;
className: HTMLAttributes<HTMLButtonElement>["className"];
}
function EditorActionButton({
onClick,
disabled,
className,
children,
}: React.PropsWithChildren<EditorActionButtonProps>) {
return (
<button
type="button"
onClick={onClick}
disabled={disabled}
className={cn(
"text-sm py-0.5 rounded w-20",
"hover:bg-neutral-700 disabled:opacity-50 disabled:cursor-not-allowed",
className,
)}
>
{children}
</button>
);
}
interface EditorActionsProps {
onSave: () => void;
onDiscard: () => void;
isDisabled: boolean;
}
export function EditorActions({
onSave,
onDiscard,
isDisabled,
}: EditorActionsProps) {
return (
<div className="flex gap-2">
<EditorActionButton
onClick={onSave}
disabled={isDisabled}
className="bg-neutral-800 disabled:hover:bg-neutral-800"
>
Save
</EditorActionButton>
<EditorActionButton
onClick={onDiscard}
disabled={isDisabled}
className="border border-neutral-800 disabled:hover:bg-transparent"
>
Discard
</EditorActionButton>
</div>
);
}

View File

@@ -27,6 +27,7 @@ interface FilesContextType {
modifiedFiles: Record<string, string>;
modifyFileContent: (path: string, content: string) => void;
saveFileContent: (path: string) => string | undefined;
discardChanges: (path: string) => void;
}
const FilesContext = React.createContext<FilesContextType | undefined>(
@@ -62,19 +63,25 @@ function FilesProvider({ children }: FilesProviderProps) {
[files, modifiedFiles],
);
const discardChanges = React.useCallback((path: string) => {
setModifiedFiles((prev) => {
const newModifiedFiles = { ...prev };
delete newModifiedFiles[path];
return newModifiedFiles;
});
}, []);
const saveFileContent = React.useCallback(
(path: string): string | undefined => {
const content = modifiedFiles[path];
if (content) {
setFiles((prev) => ({ ...prev, [path]: content }));
const newModifiedFiles = { ...modifiedFiles };
delete newModifiedFiles[path];
setModifiedFiles(newModifiedFiles);
discardChanges(path);
}
return content;
},
[files, modifiedFiles, selectedPath],
[files, modifiedFiles, selectedPath, discardChanges],
);
const value = React.useMemo(
@@ -88,6 +95,7 @@ function FilesProvider({ children }: FilesProviderProps) {
modifiedFiles,
modifyFileContent,
saveFileContent,
discardChanges,
}),
[
paths,
@@ -99,6 +107,7 @@ function FilesProvider({ children }: FilesProviderProps) {
modifiedFiles,
modifyFileContent,
saveFileContent,
discardChanges,
],
);

View File

@@ -13,6 +13,7 @@ import OpenHands from "#/api/open-hands";
import { useSocket } from "#/context/socket";
import CodeEditorCompoonent from "./code-editor-component";
import { useFiles } from "#/context/files";
import { EditorActions } from "#/components/editor-actions";
export const clientLoader = async () => {
const token = localStorage.getItem("token");
@@ -48,7 +49,13 @@ export function ErrorBoundary() {
function CodeEditor() {
const { token } = useLoaderData<typeof clientLoader>();
const { runtimeActive } = useSocket();
const { setPaths } = useFiles();
const {
setPaths,
selectedPath,
modifiedFiles,
saveFileContent: saveNewFileContent,
discardChanges,
} = useFiles();
const agentState = useSelector(
(state: RootState) => state.agent.curAgentState,
@@ -68,10 +75,38 @@ function CodeEditor() {
[agentState],
);
const handleSave = async () => {
if (selectedPath) {
const content = saveNewFileContent(selectedPath);
if (content && token) {
try {
await OpenHands.saveFile(token, selectedPath, content);
} catch (error) {
// handle error
}
}
}
};
const handleDiscard = () => {
if (selectedPath) discardChanges(selectedPath);
};
return (
<div className="flex h-full w-full bg-neutral-900 relative">
<FileExplorer />
<div className="flex flex-col min-h-0 w-full pt-3">
<div className="flex flex-col min-h-0 w-full">
{selectedPath && (
<div className="flex w-full items-center justify-between self-end p-2">
<span className="text-sm text-neutral-500">{selectedPath}</span>
<EditorActions
onSave={handleSave}
onDiscard={handleDiscard}
isDisabled={!isEditingAllowed || !modifiedFiles[selectedPath]}
/>
</div>
)}
<div className="flex grow items-center justify-center">
<CodeEditorCompoonent isReadOnly={!isEditingAllowed} />
</div>