Skip to content

Commit

Permalink
Improve file browser
Browse files Browse the repository at this point in the history
  • Loading branch information
prasunanand committed Dec 20, 2024
1 parent 6d5e97f commit 20d31f7
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 73 deletions.
7 changes: 4 additions & 3 deletions content/content_api_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func ContentDeleteAPIHandler(w http.ResponseWriter, req *http.Request) {
func ContentCreateAPIHandler(w http.ResponseWriter, req *http.Request) {
var contentPayload ContentPayload
_ = json.NewDecoder(req.Body).Decode(&contentPayload)

data := createContent(contentPayload)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
Expand All @@ -114,10 +115,10 @@ func ContentRenameAPIHandler(w http.ResponseWriter, req *http.Request) {
var renameContentPayload RenameContentPayload
_ = json.NewDecoder(req.Body).Decode(&renameContentPayload)

oldPath := renameContentPayload.OldPath
log.Info().Msgf("old path : %s", oldPath)
oldName := renameContentPayload.OldName
log.Info().Msgf("old path : %s", oldName)

rename(oldPath, renameContentPayload.Path)
rename(renameContentPayload.ParentDir, oldName, renameContentPayload.NewName)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
Expand Down
12 changes: 6 additions & 6 deletions content/content_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,19 +200,19 @@ func createContent(payload ContentPayload) models.ContentModel {
if payload.Extension == ".ipynb" {
model.ContentType = "notebook"
filename := "untitled.ipynb"
model.Path = GetOSPath(filename)
model.Path = GetOSPath(payload.ParentDir + "/" + filename)
model.Name = filename
newUntitledFile(model)
} else if payload.ContentType == "directory" {
model.ContentType = "directory"
filename := "untitled_directory"
model.Path = GetOSPath(filename)
model.Path = GetOSPath(payload.ParentDir + "/" + filename)
model.Name = filename
CreateDirectory(model.Path)
} else {
model.ContentType = "file"
filename := "untitled.txt"
model.Path = GetOSPath(filename)
model.Path = GetOSPath(payload.ParentDir + "/" + filename)
model.Name = filename
newUntitledNotebook(model)
}
Expand Down Expand Up @@ -260,10 +260,10 @@ func CreateDirectory(dirPath string) error {
return nil
}

func rename(oldName, newName string) error {
err := os.Rename(GetOSPath(oldName), GetOSPath(newName))
func rename(parentDir, oldName, newName string) error {
err := os.Rename(GetOSPath(parentDir+"/"+oldName), GetOSPath(parentDir+"/"+newName))
if err != nil {
return err
log.Info().Msgf("error is %s", err)
}
return nil
}
Expand Down
6 changes: 4 additions & 2 deletions content/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ type (
ContentPayload struct {
Extension string `json:"ext"`
ContentType string `json:"type"`
ParentDir string `json:"parent_dir"`
}

RenameContentPayload struct {
OldPath string `json:"old_path"`
Path string `json:"path"`
ParentDir string `json:"parent_dir"`
OldName string `json:"old_name"`
NewName string `json:"new_name"`
}

ContentUpdateRequest struct {
Expand Down
8 changes: 6 additions & 2 deletions ui/src/ide/sidebar/ContextMenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
border: 1px solid #ccc;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
z-index: 1000;
width: 150px;
width: 12 0px;
border-radius: 5px;
}

.context-menu-item {
padding: 8px 12px;
padding: 4px 12px;
cursor: pointer;
color: #473990;
font-size: 12px;
font-weight: 400;
}

.context-menu-item:hover {
Expand Down
183 changes: 123 additions & 60 deletions ui/src/ide/sidebar/FileBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import ContextMenu from './ContextMenu';
import getFileExtension from '../utils';
import { useAtom } from 'jotai';
import { userNameAtom } from '../../store/AppState';
import { v4 as uuidv4 } from 'uuid';


interface IContent {
id: string;
type: string;
path: string;
name: string;
Expand All @@ -30,13 +32,16 @@ export default function FileBrowser({ sendDataToParent, display }: FileBrowserPr
body: JSON.stringify({ path: cwd }),
});
const resJson = await res.json();
resJson.content.forEach((item) => {
item.id = uuidv4();
});
setContents(resJson.content);

const res2 = await fetch(BaseApiUrl + '/api/info');
const resJson2 = await res2.json();
setProjectName(resJson2.project.toUpperCase());
setUserName(resJson2.username)

};

const handleFileClick = (name: string, path: string, type: string) => {
Expand All @@ -46,7 +51,7 @@ export default function FileBrowser({ sendDataToParent, display }: FileBrowserPr
const createNewFile = async () => {
await fetch(BaseApiUrl + '/api/contents/create', {
method: 'POST',
body: JSON.stringify({ ext: '.py', type: 'file' }),
body: JSON.stringify({ parent_dir: "", ext: '.py', type: 'file' }),
});
FetchData();
};
Expand Down Expand Up @@ -88,13 +93,14 @@ export default function FileBrowser({ sendDataToParent, display }: FileBrowserPr
{contents.map((content, index) => (
content.type === 'directory' ? (
<DirectoryItem
key={content.name}
key={content.id}
data={content}
sendDataToParent={sendDataToParent}
/>
) : (
<FileItem
key={content.name}
parentDir={cwd}
key={content.id}
content={content}
handleFileClick={handleFileClick}
/>
Expand All @@ -107,37 +113,13 @@ export default function FileBrowser({ sendDataToParent, display }: FileBrowserPr
);
}

const FileItem = ({ content, handleFileClick }: { content: IContent;
handleFileClick: (name: string, path: string, type: string) => void ,
}) => {
const [isEditing, setIsEditing] = useState(false);
const [text, setText] = useState(content.name);
const [menuPosition, setMenuPosition] = useState<{ xPos: number; yPos: number } | null>(null);
const [isMenuVisible, setIsMenuVisible] = useState(false);

const renameContent = async () => {
setIsEditing(false)
await fetch(BaseApiUrl + '/api/contents/rename', {
method: 'POST',
body: JSON.stringify({ old_path: content.name, new_path: text }),
});
};

const menuItems = [
{
label: 'Rename',
action: (path: string) => {
// e.stopPropagation(); // Stop the click event from propagating
setIsEditing(true);

}
},
// { label: 'Delete', action: async () => await deleteFile(data.path) }
];


const getIconToLoad = () => {
const extension = getFileExtension(content.name);
const FileItem = (
{ parentDir, content, handleFileClick }: {
parentDir: string; content: IContent;
handleFileClick: (name: string, path: string, type: string) => void,
}) => {
const getIconToLoad = (fileName) => {
const extension = getFileExtension(fileName);
const iconMap: { [key: string]: string } = {
go: './images/editor/go-icon.svg',
mod: './images/editor/go-icon.svg',
Expand All @@ -161,26 +143,84 @@ const FileItem = ({ content, handleFileClick }: { content: IContent;
};
return extension != null ? iconMap[extension] : './images/editor/go-icon.svg';
};
const [isEditing, setIsEditing] = useState(false);
const [contentName, setContentName] = useState(content.name);
const [text, setText] = useState(content.name);
const [menuPosition, setMenuPosition] = useState<{ xPos: number; yPos: number } | null>(null);
const [isMenuVisible, setIsMenuVisible] = useState(false);
const [icon, setIcon] = useState(getIconToLoad(content.name))
const [isDeleted, setIsDeleted] = useState(false)

const renameContent = async () => {
setIsEditing(false)
await fetch(BaseApiUrl + '/api/contents/rename', {
method: 'POST',
body: JSON.stringify({ parent_dir: parentDir, old_name: contentName, new_name: text }),
});
setContentName(text)
setIcon(getIconToLoad(text))
};

const deleteContent = async () => {
await fetch(BaseApiUrl + '/api/contents', {
method: 'DELETE',
body: JSON.stringify({ path: getPath() }),
});
setIsDeleted(true)
};

const menuItems = [
{
label: 'Rename',
action: (path: string) => {
// e.stopPropagation(); // Stop the click event from propagating
setIsEditing(true);
}
},
{
label: 'Delete',
action: (path: string) => {
// e.stopPropagation(); // Stop the click event from propagating
deleteContent()

}
},
];




const handleRightClick = (e: React.MouseEvent, path: string) => {
e.preventDefault();
setMenuPosition({ xPos: e.pageX, yPos: e.pageY });
setIsMenuVisible(true);
};

const handleClick = (name: string, path: string, type:string) => {
const getPath = () =>{
if (parentDir === ""){
return text
}else{
return parentDir + "/" + text
}
}

const handleClick = (name: string, path: string, type: string) => {
if (!isMenuVisible) {
handleFileClick(name, path, type)
handleFileClick(name, getPath(), type)
}
}

if (isDeleted){
return <></>
}

return (
<li className='fileItem'>
<a
onClick={() => handleClick(content.name, content.path, content.type)}
onClick={() => handleClick(text, content.path, content.type)}
onContextMenu={(e) => handleRightClick(e, content.path)}
>
<img src={getIconToLoad()} alt='' />
<img src={icon} alt='' />
{isEditing ? (
<input
type='text'
Expand All @@ -194,20 +234,20 @@ const FileItem = ({ content, handleFileClick }: { content: IContent;
<span>{text}</span>
)}
{isMenuVisible && menuPosition && (
<ContextMenu
xPos={menuPosition.xPos}
yPos={menuPosition.yPos}
items={menuItems}
path={content.path}
onClose={() => setIsMenuVisible(false)}
/>
)}
<ContextMenu
xPos={menuPosition.xPos}
yPos={menuPosition.yPos}
items={menuItems}
path={content.path}
onClose={() => setIsMenuVisible(false)}
/>
)}
</a>
</li>
);
};

const DirectoryItem = ({data, sendDataToParent }) => {
const DirectoryItem = ({ data, sendDataToParent }) => {
const [isEditing, setIsEditing] = useState(false);
const [content, setContent] = useState(data)
const [text, setText] = useState(data.name);
Expand All @@ -223,12 +263,35 @@ const DirectoryItem = ({data, sendDataToParent }) => {
body: JSON.stringify({ path }),
});
const resJson = await res.json();
resJson.content.forEach((item) => {
item.id = uuidv4();
});
setContent(resJson)
};

const createNewFile = async (path: string) => {
console.log("add file")
await fetch(BaseApiUrl + '/api/contents/create', {
method: 'POST',
body: JSON.stringify({ parent_dir: path, ext: '.py', type: 'file' }),
});

const res = await fetch(BaseApiUrl + '/api/contents?type=notebook&hash=0', {
method: 'POST',
body: JSON.stringify({ path }),
});
const resJson = await res.json();
resJson.content.forEach((item) => {
item.id = uuidv4();
});
setContent(resJson)
};

const menuItems = [
{ label: 'Rename', action: () => setIsEditing(true) },
// { label: 'Delete', action: async () => await deleteFile(data.path) }
{ label: 'Add file', action: (path: string) => createNewFile(path) },
{ label: 'Add Notebook', action: (path: string) => console.log("add notebook") },
{ label: 'Add directory', action: (path: string) => console.log("add directory" + path) }
];

const handleRightClick = (e: React.MouseEvent, path: string) => {
Expand Down Expand Up @@ -263,17 +326,17 @@ const DirectoryItem = ({data, sendDataToParent }) => {
onClose={() => setIsMenuVisible(false)}
/>
)}
<ul className='file-list list-unstyled'>
{isCollapsed && content.content !== null && content.content.map((content, index) => (
content.type === 'directory' ? (
<DirectoryItem key={content.name}
sendDataToParent={sendDataToParent}
data={content} />
) : (
<FileItem key={content.name} content={content} handleFileClick={sendDataToParent}/>
)
))}
</ul>
<ul className='file-list list-unstyled'>
{isCollapsed && content.content !== null && content.content.map((content, index) => (
content.type === 'directory' ? (
<DirectoryItem key={content.id}
sendDataToParent={sendDataToParent}
data={content} />
) : (
<FileItem parentDir={data.name} key={content.id} content={content} handleFileClick={sendDataToParent} />
)
))}
</ul>
</li>
);
};

0 comments on commit 20d31f7

Please sign in to comment.