Projektordner im ⚙︎-Dialog änderbar: Registry-Eintrag neu zuordnen, autoMemoryDirectory + Edit-Permission ziehen auf den neuen Root um; gesperrt bei laufender Session
This commit is contained in:
@@ -559,6 +559,53 @@ fn create_project_full_in(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verlegt den Projektordner: Registry-Eintrag auf den neuen Pfad; Verweise
|
||||||
|
/// auf den alten Root in der settings.json (autoMemoryDirectory,
|
||||||
|
/// Edit-Permission) ziehen mit. Verschoben wird nichts — der neue Ordner
|
||||||
|
/// muss existieren.
|
||||||
|
fn set_project_dir_in(paths: &Paths, name: &str, dir: &str) -> Result<(), String> {
|
||||||
|
check_name(name)?;
|
||||||
|
let new_dir = expand_home(paths, dir);
|
||||||
|
if !new_dir.is_dir() {
|
||||||
|
return Err(format!("Ordner nicht gefunden: {}", new_dir.display()));
|
||||||
|
}
|
||||||
|
let mut reg = load_registry(paths)?;
|
||||||
|
let old_dir = reg
|
||||||
|
.get(name)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| format!("Projekt nicht registriert: {name}"))?;
|
||||||
|
if old_dir == new_dir {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
reg.insert(name.to_string(), new_dir.clone());
|
||||||
|
save_registry(paths, ®)?;
|
||||||
|
|
||||||
|
let sp = settings_path(&new_dir);
|
||||||
|
if !sp.is_file() {
|
||||||
|
return Ok(()); // importierte Projekte ohne settings.json
|
||||||
|
}
|
||||||
|
let old_c = contract_home(paths, &old_dir);
|
||||||
|
let new_c = contract_home(paths, &new_dir);
|
||||||
|
let raw = fs::read_to_string(&sp).map_err(|e| format!("{}: {e}", sp.display()))?;
|
||||||
|
let mut v: serde_json::Value =
|
||||||
|
serde_json::from_str(&raw).map_err(|e| format!("{}: {e}", sp.display()))?;
|
||||||
|
if let Some(mem) = v["autoMemoryDirectory"].as_str() {
|
||||||
|
if let Some(rest) = mem.strip_prefix(&old_c) {
|
||||||
|
v["autoMemoryDirectory"] = serde_json::json!(format!("{new_c}{rest}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(allow) = v["permissions"]["allow"].as_array_mut() {
|
||||||
|
let old_edit = format!("Edit({old_c}/**)");
|
||||||
|
for e in allow.iter_mut() {
|
||||||
|
if e.as_str() == Some(&old_edit) {
|
||||||
|
*e = serde_json::json!(format!("Edit({new_c}/**)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let raw = serde_json::to_string_pretty(&v).map_err(|e| e.to_string())?;
|
||||||
|
fs::write(&sp, raw + "\n").map_err(|e| format!("{}: {e}", sp.display()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Nimmt einen bestehenden Ordner als Projekt auf; der Name ist der Ordnername.
|
/// Nimmt einen bestehenden Ordner als Projekt auf; der Name ist der Ordnername.
|
||||||
fn add_project_in(paths: &Paths, path: &str) -> Result<(), String> {
|
fn add_project_in(paths: &Paths, path: &str) -> Result<(), String> {
|
||||||
let dir = expand_home(paths, path);
|
let dir = expand_home(paths, path);
|
||||||
@@ -1544,6 +1591,15 @@ fn remove_project(name: String) -> Result<(), String> {
|
|||||||
unregister_project(&Paths::real(), &name)
|
unregister_project(&Paths::real(), &name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Projektordner neu zuordnen; bei laufender Session gesperrt.
|
||||||
|
#[tauri::command]
|
||||||
|
fn set_project_dir(project: String, dir: String) -> Result<(), String> {
|
||||||
|
if is_running(&project) {
|
||||||
|
return Err(format!("{project} läuft noch — erst beenden"));
|
||||||
|
}
|
||||||
|
set_project_dir_in(&Paths::real(), &project, &dir)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn add_work_dir(project: String, dir: String) -> Result<(), String> {
|
fn add_work_dir(project: String, dir: String) -> Result<(), String> {
|
||||||
add_work_dir_in(&Paths::real(), &project, &dir)
|
add_work_dir_in(&Paths::real(), &project, &dir)
|
||||||
@@ -1906,6 +1962,7 @@ fn main_builder() -> tauri::Builder<tauri::Wry> {
|
|||||||
remove_project,
|
remove_project,
|
||||||
delete_project,
|
delete_project,
|
||||||
project_work_dirs,
|
project_work_dirs,
|
||||||
|
set_project_dir,
|
||||||
add_work_dir,
|
add_work_dir,
|
||||||
remove_work_dir,
|
remove_work_dir,
|
||||||
list_pools,
|
list_pools,
|
||||||
@@ -2753,6 +2810,24 @@ mod tests {
|
|||||||
assert!(p.home.join("projects/extra").is_dir());
|
assert!(p.home.join("projects/extra").is_dir());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn projektordner_verlegen() {
|
||||||
|
let p = tmp_paths();
|
||||||
|
create_project_full_in(&p, "proj", None, None, None, false, TerminalConfig::default(), false)
|
||||||
|
.unwrap();
|
||||||
|
// Nutzer hat den Ordner selbst verschoben; die App ordnet nur neu zu.
|
||||||
|
let new_dir = p.home.join("elsewhere").join("proj");
|
||||||
|
fs::create_dir_all(p.home.join("elsewhere")).unwrap();
|
||||||
|
fs::rename(p.projects_dir().join("proj"), &new_dir).unwrap();
|
||||||
|
set_project_dir_in(&p, "proj", &new_dir.to_string_lossy()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(project_dir(&p, "proj").unwrap(), new_dir);
|
||||||
|
let raw = fs::read_to_string(settings_path(&new_dir)).unwrap();
|
||||||
|
assert!(raw.contains("~/elsewhere/proj/memory"));
|
||||||
|
assert!(raw.contains("Edit(~/elsewhere/proj/**)"));
|
||||||
|
assert!(!raw.contains("claude-projects/proj"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn projekt_loeschen_unbekannt_scheitert() {
|
fn projekt_loeschen_unbekannt_scheitert() {
|
||||||
let p = tmp_paths();
|
let p = tmp_paths();
|
||||||
|
|||||||
@@ -182,6 +182,22 @@ async function openSettings(p: Project) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Projektordner neu zuordnen (Root, in dem die Session startet); verschoben
|
||||||
|
// wird nichts — der gewählte Ordner ist der neue Ort des Projekts.
|
||||||
|
async function changeProjectDir() {
|
||||||
|
const s = settings.value!;
|
||||||
|
const dir = await open({ directory: true, multiple: false });
|
||||||
|
if (typeof dir !== "string") return;
|
||||||
|
try {
|
||||||
|
await invoke("set_project_dir", { project: s.name, dir });
|
||||||
|
await refresh();
|
||||||
|
const p = projects.value.find((x) => x.name === s.name);
|
||||||
|
if (p) s.path = p.path;
|
||||||
|
} catch (e) {
|
||||||
|
error.value = String(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Arbeitsordner schreiben direkt in die Projekt-settings.json
|
// Arbeitsordner schreiben direkt in die Projekt-settings.json
|
||||||
// (additionalDirectories + Edit-Permission) — kein Speichern-Schritt.
|
// (additionalDirectories + Edit-Permission) — kein Speichern-Schritt.
|
||||||
async function addWorkDir() {
|
async function addWorkDir() {
|
||||||
@@ -624,6 +640,7 @@ onUnmounted(() => window.clearInterval(timer));
|
|||||||
<span class="workdirs">
|
<span class="workdirs">
|
||||||
<span class="icon-row">
|
<span class="icon-row">
|
||||||
<span class="icon-path">{{ settings.path }}</span>
|
<span class="icon-path">{{ settings.path }}</span>
|
||||||
|
<button @click="changeProjectDir">{{ $t("projects.changeDir") }}</button>
|
||||||
</span>
|
</span>
|
||||||
<span v-for="wd in settings.workDirs" :key="wd" class="icon-row">
|
<span v-for="wd in settings.workDirs" :key="wd" class="icon-row">
|
||||||
<span class="icon-path">{{ wd }}</span>
|
<span class="icon-path">{{ wd }}</span>
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ const de = {
|
|||||||
addFolder: "Projekt importieren …",
|
addFolder: "Projekt importieren …",
|
||||||
addFolderTitle: "Bestehenden Ordner als Projekt aufnehmen",
|
addFolderTitle: "Bestehenden Ordner als Projekt aufnehmen",
|
||||||
addWorkDir: "Ordner hinzufügen …",
|
addWorkDir: "Ordner hinzufügen …",
|
||||||
|
changeDir: "Ändern …",
|
||||||
wizardTitle: "Neues Projekt",
|
wizardTitle: "Neues Projekt",
|
||||||
name: "Name",
|
name: "Name",
|
||||||
projectDir: "Projektordner",
|
projectDir: "Projektordner",
|
||||||
@@ -177,6 +178,7 @@ const en: typeof de = {
|
|||||||
addFolder: "Import project …",
|
addFolder: "Import project …",
|
||||||
addFolderTitle: "Add an existing folder as a project",
|
addFolderTitle: "Add an existing folder as a project",
|
||||||
addWorkDir: "Add folder …",
|
addWorkDir: "Add folder …",
|
||||||
|
changeDir: "Change …",
|
||||||
wizardTitle: "New project",
|
wizardTitle: "New project",
|
||||||
name: "Name",
|
name: "Name",
|
||||||
projectDir: "Project folder",
|
projectDir: "Project folder",
|
||||||
|
|||||||
Reference in New Issue
Block a user