优化web/server模式下的目录选择,现在支持目录树选择,而不是硬文本写入
This commit is contained in:
@@ -2934,6 +2934,98 @@ where
|
||||
})?
|
||||
}
|
||||
|
||||
// ─── Directory browser helpers (for web/server mode) ───
|
||||
|
||||
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
|
||||
pub async fn get_home_directory() -> Result<String, AppCommandError> {
|
||||
dirs::home_dir()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.ok_or_else(|| AppCommandError::io_error("Could not determine home directory"))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DirectoryEntry {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
pub has_children: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
|
||||
pub async fn list_directory_entries(
|
||||
path: String,
|
||||
) -> Result<Vec<DirectoryEntry>, AppCommandError> {
|
||||
let root = PathBuf::from(&path);
|
||||
if !root.is_dir() {
|
||||
return Err(AppCommandError::io_error("Path is not a directory")
|
||||
.with_detail(path));
|
||||
}
|
||||
|
||||
let mut entries: Vec<DirectoryEntry> = Vec::new();
|
||||
let read_dir = std::fs::read_dir(&root).map_err(|e| {
|
||||
AppCommandError::io_error("Failed to read directory").with_detail(e.to_string())
|
||||
})?;
|
||||
|
||||
for entry in read_dir {
|
||||
let entry = match entry {
|
||||
Ok(e) => e,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let file_type = match entry.file_type() {
|
||||
Ok(ft) => ft,
|
||||
Err(_) => continue,
|
||||
};
|
||||
// Follow symlinks: check if the resolved path is a directory
|
||||
let is_dir = if file_type.is_symlink() {
|
||||
entry.path().is_dir()
|
||||
} else {
|
||||
file_type.is_dir()
|
||||
};
|
||||
if !is_dir {
|
||||
continue;
|
||||
}
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
// Skip hidden directories (starting with '.')
|
||||
if name.starts_with('.') {
|
||||
continue;
|
||||
}
|
||||
let abs_path = entry.path().to_string_lossy().to_string();
|
||||
|
||||
// Peek into subdirectory to check if it has child directories
|
||||
let has_children = match std::fs::read_dir(entry.path()) {
|
||||
Ok(sub) => sub
|
||||
.filter_map(|e| e.ok())
|
||||
.any(|e| {
|
||||
let ft = e.file_type().ok();
|
||||
let is_sub_dir = ft.map_or(false, |ft| {
|
||||
if ft.is_symlink() {
|
||||
e.path().is_dir()
|
||||
} else {
|
||||
ft.is_dir()
|
||||
}
|
||||
});
|
||||
if !is_sub_dir {
|
||||
return false;
|
||||
}
|
||||
let sub_name = e.file_name().to_string_lossy().to_string();
|
||||
!sub_name.starts_with('.')
|
||||
}),
|
||||
Err(_) => false,
|
||||
};
|
||||
|
||||
entries.push(DirectoryEntry {
|
||||
name,
|
||||
path: abs_path,
|
||||
has_children,
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by name, case-insensitive
|
||||
entries.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
|
||||
pub async fn get_file_tree(
|
||||
path: String,
|
||||
|
||||
@@ -270,6 +270,8 @@ mod tauri_app {
|
||||
folders::save_folder_opened_conversations,
|
||||
folders::start_file_tree_watch,
|
||||
folders::stop_file_tree_watch,
|
||||
folders::get_home_directory,
|
||||
folders::list_directory_entries,
|
||||
folders::get_file_tree,
|
||||
folders::read_file_base64,
|
||||
folders::read_file_preview,
|
||||
|
||||
@@ -110,6 +110,24 @@ pub async fn get_git_branch(
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn get_home_directory() -> Result<Json<String>, AppCommandError> {
|
||||
let result = folder_commands::get_home_directory().await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ListDirectoryEntriesParams {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
pub async fn list_directory_entries(
|
||||
Json(params): Json<ListDirectoryEntriesParams>,
|
||||
) -> Result<Json<Vec<folder_commands::DirectoryEntry>>, AppCommandError> {
|
||||
let result = folder_commands::list_directory_entries(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetFileTreeParams {
|
||||
|
||||
@@ -50,6 +50,8 @@ pub fn build_router(state: Arc<AppState>, token: String, static_dir: std::path::
|
||||
.route("/create_folder_directory", post(handlers::folders::create_folder_directory))
|
||||
.route("/save_folder_opened_conversations", post(handlers::folders::save_folder_opened_conversations))
|
||||
.route("/get_git_branch", post(handlers::folders::get_git_branch))
|
||||
.route("/get_home_directory", post(handlers::folders::get_home_directory))
|
||||
.route("/list_directory_entries", post(handlers::folders::list_directory_entries))
|
||||
.route("/get_file_tree", post(handlers::folders::get_file_tree))
|
||||
.route("/start_file_tree_watch", post(handlers::folders::start_file_tree_watch))
|
||||
.route("/stop_file_tree_watch", post(handlers::folders::stop_file_tree_watch))
|
||||
|
||||
Reference in New Issue
Block a user