Modals for Delete and Rename Files and Folders #10
@ -133,6 +133,25 @@ class FileManager:
|
||||
st.error(f"File not found: {old_relative_path}")
|
||||
return False
|
||||
|
||||
def delete_folder(self, relative_path) -> bool:
|
||||
folder_path = (self.base_path / relative_path).resolve()
|
||||
|
||||
if not str(folder_path).startswith(str(self.base_path.resolve())):
|
||||
st.error(f"Access denied: {relative_path}")
|
||||
return False
|
||||
|
||||
if not folder_path.exists():
|
||||
st.error(f"Folder not found: {relative_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
import shutil
|
||||
shutil.rmtree(folder_path)
|
||||
return True
|
||||
except Exception as e:
|
||||
st.error(f"Error deleting folder {relative_path}: {str(e)}")
|
||||
return False
|
||||
|
||||
def delete_file(self, relative_path):
|
||||
file_path = Path(relative_path).resolve()
|
||||
|
||||
|
||||
@ -101,71 +101,57 @@ def render_editor():
|
||||
if not st.session_state.open_files:
|
||||
st.info("Please select a file to edit.")
|
||||
return
|
||||
else:
|
||||
fm = FileManager()
|
||||
|
||||
try:
|
||||
active_index = st.session_state.open_files.index(st.session_state.active_file)
|
||||
except (ValueError, KeyError):
|
||||
active_index = 0
|
||||
|
||||
st.session_state.active_tab = active_index
|
||||
|
||||
tab_names = [Path(f).name for f in st.session_state.open_files]
|
||||
tabs = st.tabs(tab_names)
|
||||
|
||||
for idx, file_path in enumerate(st.session_state.open_files):
|
||||
with tabs[idx]:
|
||||
if idx == st.session_state.active_tab:
|
||||
st.session_state.active_file = file_path
|
||||
fm = FileManager()
|
||||
|
||||
if file_path not in st.session_state.files_content:
|
||||
st.session_state.files_content[file_path] = fm.read_file(Path(file_path))
|
||||
|
||||
file_language = LANG_MAP.get(Path(file_path).suffix, "text")
|
||||
|
||||
code = st_ace.st_ace(
|
||||
value=st.session_state.files_content[file_path],
|
||||
language=file_language,
|
||||
theme="monokai",
|
||||
key=f"code_editor_{file_path}",
|
||||
auto_update=True,
|
||||
height=400,
|
||||
)
|
||||
# ── Tab bar via st.tabs() ─────────────────────────────────────────────────
|
||||
tab_names = [Path(f).name for f in st.session_state.open_files]
|
||||
tabs = st.tabs(tab_names)
|
||||
|
||||
if code != st.session_state.files_content[file_path]:
|
||||
st.session_state.files_content[file_path] = code
|
||||
|
||||
cols = st.columns([1, 1, 1, 1])
|
||||
with cols[0]:
|
||||
if st.button("Save Changes", key=f"save_{file_path}"):
|
||||
content = st.session_state.files_content[file_path]
|
||||
if fm.save_file(file_path, content):
|
||||
st.success("File saved successfully!")
|
||||
|
||||
with cols[1]:
|
||||
if st.button("Close File", key=f"close_{file_path}"):
|
||||
st.session_state.open_files.remove(file_path)
|
||||
del st.session_state.files_content[file_path]
|
||||
st.success("File closed successfully!")
|
||||
|
||||
if st.session_state.active_file == file_path:
|
||||
st.session_state.active_file = (
|
||||
st.session_state.open_files[0]
|
||||
if st.session_state.open_files
|
||||
else None
|
||||
)
|
||||
st.rerun() # Refresh the page to update the UI
|
||||
for idx, file_path in enumerate(st.session_state.open_files):
|
||||
with tabs[idx]:
|
||||
if file_path not in st.session_state.files_content:
|
||||
st.session_state.files_content[file_path] = fm.read_file(Path(file_path))
|
||||
|
||||
with cols[2]:
|
||||
if st.button("Rename File", key=f"rename_{file_path}"):
|
||||
_rename_dialog(file_path)
|
||||
file_language = LANG_MAP.get(Path(file_path).suffix, "text")
|
||||
|
||||
with cols[3]:
|
||||
if st.button("Delete File", key=f"delete_{file_path}"):
|
||||
_delete_dialog(file_path)
|
||||
|
||||
if st.button("▶ Run Code", key="run_code"):
|
||||
code = st_ace.st_ace(
|
||||
value=st.session_state.files_content[file_path],
|
||||
language=file_language,
|
||||
theme="monokai",
|
||||
key=f"code_editor_{file_path}",
|
||||
auto_update=True,
|
||||
height=400,
|
||||
)
|
||||
|
||||
if code != st.session_state.files_content[file_path]:
|
||||
st.session_state.files_content[file_path] = code
|
||||
|
||||
cols = st.columns([1, 1, 1, 1])
|
||||
with cols[0]:
|
||||
if st.button("Save Changes", key=f"save_{file_path}"):
|
||||
if fm.save_file(file_path, code):
|
||||
st.success("File saved successfully!")
|
||||
|
||||
with cols[1]:
|
||||
if st.button("Close File", key=f"close_{file_path}"):
|
||||
st.session_state.open_files.remove(file_path)
|
||||
st.session_state.files_content.pop(file_path, None)
|
||||
st.session_state.active_file = (
|
||||
st.session_state.open_files[0]
|
||||
if st.session_state.open_files else None
|
||||
)
|
||||
st.rerun()
|
||||
|
||||
with cols[2]:
|
||||
if st.button("Rename File", key=f"rename_{file_path}"):
|
||||
_rename_dialog(file_path)
|
||||
|
||||
with cols[3]:
|
||||
if st.button("Delete File", key=f"delete_{file_path}"):
|
||||
_delete_dialog(file_path)
|
||||
|
||||
if st.button("▶ Run Code", key="run_code"):
|
||||
result = run_active_file()
|
||||
if not result:
|
||||
st.stop()
|
||||
|
||||
@ -24,6 +24,64 @@ SUFFIX_MAP = {
|
||||
"default": "📄" # Unbekannt
|
||||
}
|
||||
|
||||
|
||||
# ── Modals ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@st.dialog("Delete Folder")
|
||||
def _delete_folder_dialog(folder_rel: str, folder_name: str):
|
||||
st.warning(f"Delete **{folder_name}** and all its contents? This cannot be undone.")
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
if st.button("Delete", type="primary", use_container_width=True):
|
||||
if fm.delete_folder(folder_rel):
|
||||
st.session_state.selected_folder = None
|
||||
st.session_state.selected_folder_rel = None
|
||||
st.rerun()
|
||||
else:
|
||||
st.error("Delete failed.")
|
||||
with col2:
|
||||
if st.button("Cancel", use_container_width=True):
|
||||
st.rerun()
|
||||
|
||||
|
||||
@st.dialog("Add File")
|
||||
def _add_file_dialog(parent_path: str = ""):
|
||||
name = st.text_input("File name:", placeholder="e.g. script.py")
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
if st.button("Create", type="primary", use_container_width=True):
|
||||
if not name.strip():
|
||||
st.warning("Please enter a file name.")
|
||||
elif "/" in name or "\\" in name:
|
||||
st.warning("Name must not contain slashes.")
|
||||
else:
|
||||
if fm.create_file(parent_path, name.strip()):
|
||||
st.rerun()
|
||||
with col2:
|
||||
if st.button("Cancel", use_container_width=True):
|
||||
st.rerun()
|
||||
|
||||
|
||||
@st.dialog("Add Folder")
|
||||
def _add_folder_dialog(parent_path: str = ""):
|
||||
name = st.text_input("Folder name:", placeholder="e.g. utils")
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
if st.button("Create", type="primary", use_container_width=True):
|
||||
if not name.strip():
|
||||
st.warning("Please enter a folder name.")
|
||||
elif "/" in name or "\\" in name:
|
||||
st.warning("Name must not contain slashes.")
|
||||
else:
|
||||
if fm.create_folder(parent_path, name.strip()):
|
||||
st.rerun()
|
||||
with col2:
|
||||
if st.button("Cancel", use_container_width=True):
|
||||
st.rerun()
|
||||
|
||||
|
||||
# ── File tree ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def build_arborist_tree(tree, parent_path=Path()):
|
||||
nodes = []
|
||||
|
||||
@ -47,7 +105,8 @@ def build_arborist_tree(tree, parent_path=Path()):
|
||||
"key": f"key_{node_id}"
|
||||
})
|
||||
|
||||
return nodes
|
||||
return nodes
|
||||
|
||||
|
||||
def render_filetree_arborist(tree):
|
||||
data = build_arborist_tree(tree)
|
||||
@ -64,30 +123,32 @@ def render_filetree_arborist(tree):
|
||||
return selected
|
||||
|
||||
|
||||
# ── Sidebar ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def render_sidebar():
|
||||
st.sidebar.title("Navigation")
|
||||
|
||||
|
||||
navigation_section = st.sidebar.container()
|
||||
file_explorer_section = st.sidebar.container()
|
||||
|
||||
|
||||
with navigation_section:
|
||||
st.radio(
|
||||
"Options",
|
||||
["Chat with AI Assistant", "Code Editor"],
|
||||
key=f"radio_interface_options")
|
||||
"Options",
|
||||
["Chat with AI Assistant", "Code Editor"],
|
||||
key="radio_interface_options")
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
|
||||
with file_explorer_section:
|
||||
st.subheader("File Explorer")
|
||||
tree = fm.get_file_tree()
|
||||
workspace = st.container()
|
||||
add_more = st.container()
|
||||
|
||||
|
||||
with workspace:
|
||||
with st.container(border=False):
|
||||
if not tree:
|
||||
st.info("Workspace is empty.")
|
||||
st.info("Workspace is empty.")
|
||||
else:
|
||||
selected = render_filetree_arborist(tree)
|
||||
|
||||
@ -97,81 +158,45 @@ def render_sidebar():
|
||||
if st.session_state.last_selected != selected_path:
|
||||
st.session_state.last_selected = selected_path
|
||||
abs_path = fm.base_path / selected_path
|
||||
|
||||
|
||||
if abs_path.is_file():
|
||||
if str(abs_path) not in st.session_state.open_files:
|
||||
st.session_state.open_files.append(str(abs_path))
|
||||
|
||||
st.session_state.active_file = str(abs_path)
|
||||
st.session_state.selected_folder = None
|
||||
st.session_state.selected_folder_rel = None
|
||||
file_str = str(abs_path)
|
||||
if file_str not in st.session_state.open_files:
|
||||
st.session_state.open_files.append(file_str)
|
||||
st.session_state.active_file = file_str
|
||||
st.rerun()
|
||||
|
||||
elif abs_path.is_dir():
|
||||
st.session_state.selected_folder = str(abs_path)
|
||||
selected_folder = st.session_state.selected_folder
|
||||
with st.popover(
|
||||
f"📁 {Path(selected_folder).name}",
|
||||
use_container_width=True,
|
||||
key=f"{selected_folder}_popover_options"):
|
||||
action = st.radio(
|
||||
"Action",
|
||||
["New File", "New Folder"],
|
||||
key=f"radio_folder_options",
|
||||
label_visibility="collapsed")
|
||||
|
||||
new_name = st.text_input("Name", key=f"input_folder_options")
|
||||
|
||||
btn_cols = st.columns([1,1,1])
|
||||
with btn_cols[0]:
|
||||
if st.button("Create", key=f"btn_create_subfile_or_subfolder"):
|
||||
if "/" in new_name or "\\" in new_name:
|
||||
st.warning("Do not include slashes in names!")
|
||||
elif new_name:
|
||||
if action == "New Folder":
|
||||
fm.create_folder(selected_path, new_name)
|
||||
else:
|
||||
fm.create_file(selected_path, new_name)
|
||||
st.success(f"{action} created!")
|
||||
st.session_state.selected_folder = None
|
||||
st.rerun()
|
||||
st.session_state.selected_folder_rel = selected_path
|
||||
st.rerun()
|
||||
|
||||
with btn_cols[1]:
|
||||
if st.button("Cancel", key=f"cancel_folder_options"):
|
||||
st.session_state.selected_folder = None
|
||||
st.rerun()
|
||||
# Folder actions — rendered outside the selection block so they persist across reruns
|
||||
if st.session_state.get("selected_folder"):
|
||||
folder_name = Path(st.session_state.selected_folder).name
|
||||
folder_rel = st.session_state.selected_folder_rel
|
||||
|
||||
with st.container(border=True):
|
||||
st.caption(f"📁 {folder_name}")
|
||||
if st.button("Add File", key="btn_add_file_in_folder", use_container_width=True):
|
||||
_add_file_dialog(folder_rel)
|
||||
if st.button("Add Folder", key="btn_add_folder_in_folder", use_container_width=True):
|
||||
_add_folder_dialog(folder_rel)
|
||||
if st.button("Delete Folder", key="btn_delete_folder", use_container_width=True):
|
||||
_delete_folder_dialog(folder_rel, folder_name)
|
||||
|
||||
with btn_cols[2]:
|
||||
if st.button("Delete Folder", key=f"delete_folder"):
|
||||
try:
|
||||
fm.delete_folder(selected_path)
|
||||
st.success(f"Folder '{Path(selected_folder).name}' deleted successfully!")
|
||||
st.session_state.selected_folder = None
|
||||
st.rerun()
|
||||
except Exception as e:
|
||||
st.error(f"Error deleting folder: {e}")
|
||||
|
||||
with add_more:
|
||||
with st.popover("⚙️ Explorer_Options", key=f"popover_options"):
|
||||
action = st.radio(
|
||||
"Action",
|
||||
["New File", "New Folder"],
|
||||
key=f"radio_explorer_options",
|
||||
label_visibility="collapsed")
|
||||
|
||||
new_name = st.text_input("Name", key=f"input_explorer_options")
|
||||
|
||||
if st.button("Create", key=f"btn_create_file_or_folder"):
|
||||
if "/" in new_name or "\\" in new_name:
|
||||
st.warning("Do not include slashes in names!")
|
||||
elif new_name:
|
||||
if action == "New Folder":
|
||||
fm.create_folder("", new_name)
|
||||
else:
|
||||
fm.create_file("", new_name)
|
||||
st.success(f"{action} created!")
|
||||
st.rerun()
|
||||
|
||||
return
|
||||
with st.popover("⚙️ Explorer Options", key="popover_options"):
|
||||
if st.button("Add File", key="btn_add_file", use_container_width=True):
|
||||
_add_file_dialog("")
|
||||
|
||||
if st.button("Add Folder", key="btn_add_folder", use_container_width=True):
|
||||
_add_folder_dialog("")
|
||||
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
render_sidebar()
|
||||
|
||||
@ -10,6 +10,9 @@ def init_state():
|
||||
if "selected_folder" not in st.session_state:
|
||||
st.session_state.selected_folder = None
|
||||
|
||||
if "selected_folder_rel" not in st.session_state:
|
||||
st.session_state.selected_folder_rel = None
|
||||
|
||||
# Chat manager (persists across reruns)
|
||||
if "chat_manager" not in st.session_state:
|
||||
st.session_state.chat_manager = ChatManager()
|
||||
@ -57,5 +60,6 @@ def init_state():
|
||||
st.session_state.agent_pending_action = None
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_state()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user