max / audiofiles
1 file changed,
+123 insertions,
-141 deletions
| @@ -929,105 +929,115 @@ fn draw_bulk_rename_modal(ctx: &egui::Context, state: &mut BrowserState) { | |||
| 929 | 929 | } | |
| 930 | 930 | } | |
| 931 | 931 | ||
| 932 | - | /// Draw the "New Vault" modal: text input for vault name. | |
| 933 | - | pub fn draw_vfs_create_modal(ctx: &egui::Context, state: &mut BrowserState) { | |
| 934 | - | let hint = "A vault is a separate sample collection \u{2014} like a folder, but with its own tags and analysis. Right-click inside to create sub-folders."; | |
| 935 | - | // C-3: clone the error into a local so the &mut input borrow doesn't | |
| 936 | - | // conflict with the immutable error borrow into name_modal. | |
| 937 | - | let error_owned = state.vfs_modal.name_modal_error.clone(); | |
| 938 | - | match widgets::name_modal( | |
| 939 | - | ctx, | |
| 940 | - | "New Vault", | |
| 941 | - | Some(hint), | |
| 942 | - | "Vault name:", | |
| 943 | - | &mut state.vfs_modal.vfs_create_input, | |
| 944 | - | "Create", | |
| 945 | - | error_owned.as_deref(), | |
| 946 | - | ) { | |
| 932 | + | /// Shared outcome handling for the four name modals (create/rename × vault/folder). | |
| 933 | + | /// | |
| 934 | + | /// Each modal draws the same `name_modal` widget and then treats its outcome the | |
| 935 | + | /// same way: an empty or cancelled submit closes the modal; a non-empty submit | |
| 936 | + | /// runs the create/rename op, showing its status line on success or surfacing the | |
| 937 | + | /// error inline (C-3) while leaving the modal open with the typed name intact. | |
| 938 | + | /// Only the static labels, the name buffer, `close`, and `commit` differ per modal. | |
| 939 | + | /// | |
| 940 | + | /// `close` resets the modal (hides it, or clears its rename target); `commit` | |
| 941 | + | /// performs the op and returns the status line to show, or an error string to | |
| 942 | + | /// surface inline. | |
| 943 | + | fn handle_name_modal_outcome( | |
| 944 | + | outcome: NameModalOutcome, | |
| 945 | + | state: &mut BrowserState, | |
| 946 | + | close: impl FnOnce(&mut BrowserState), | |
| 947 | + | commit: impl FnOnce(&mut BrowserState, String) -> Result<String, String>, | |
| 948 | + | ) { | |
| 949 | + | match outcome { | |
| 947 | 950 | NameModalOutcome::Submitted(name) => { | |
| 948 | 951 | if name.is_empty() { | |
| 949 | 952 | // Empty submit = no-op cancel. Close without erroring. | |
| 950 | - | state.vfs_modal.show_vfs_create = false; | |
| 953 | + | close(state); | |
| 951 | 954 | state.vfs_modal.name_modal_error = None; | |
| 952 | 955 | } else { | |
| 953 | - | match state.backend.create_vfs(&name) { | |
| 954 | - | Ok(_) => { | |
| 955 | - | state.refresh_vfs_list(); | |
| 956 | - | state.status = format!("Created vault: {name}"); | |
| 957 | - | state.vfs_modal.show_vfs_create = false; | |
| 956 | + | match commit(state, name) { | |
| 957 | + | Ok(status) => { | |
| 958 | + | state.status = status; | |
| 959 | + | close(state); | |
| 958 | 960 | state.vfs_modal.name_modal_error = None; | |
| 959 | 961 | } | |
| 960 | - | Err(e) => { | |
| 961 | - | // C-3: keep modal open; surface error inline so the | |
| 962 | - | // user can edit and retry without re-typing the name. | |
| 963 | - | state.vfs_modal.name_modal_error = Some(format!("{e}")); | |
| 964 | - | } | |
| 962 | + | // C-3: keep the modal open; surface the error inline so the | |
| 963 | + | // user can edit and retry without re-typing the name. | |
| 964 | + | Err(msg) => state.vfs_modal.name_modal_error = Some(msg), | |
| 965 | 965 | } | |
| 966 | 966 | } | |
| 967 | 967 | } | |
| 968 | 968 | NameModalOutcome::Cancelled => { | |
| 969 | - | state.vfs_modal.show_vfs_create = false; | |
| 969 | + | close(state); | |
| 970 | 970 | state.vfs_modal.name_modal_error = None; | |
| 971 | 971 | } | |
| 972 | 972 | NameModalOutcome::None => {} | |
| 973 | 973 | } | |
| 974 | 974 | } | |
| 975 | 975 | ||
| 976 | + | /// Draw the "New Vault" modal: text input for vault name. | |
| 977 | + | pub fn draw_vfs_create_modal(ctx: &egui::Context, state: &mut BrowserState) { | |
| 978 | + | let hint = "A vault is a separate sample collection \u{2014} like a folder, but with its own tags and analysis. Right-click inside to create sub-folders."; | |
| 979 | + | // C-3: clone the error into a local so the &mut input borrow doesn't | |
| 980 | + | // conflict with the immutable error borrow into name_modal. | |
| 981 | + | let error_owned = state.vfs_modal.name_modal_error.clone(); | |
| 982 | + | let outcome = widgets::name_modal( | |
| 983 | + | ctx, | |
| 984 | + | "New Vault", | |
| 985 | + | Some(hint), | |
| 986 | + | "Vault name:", | |
| 987 | + | &mut state.vfs_modal.vfs_create_input, | |
| 988 | + | "Create", | |
| 989 | + | error_owned.as_deref(), | |
| 990 | + | ); | |
| 991 | + | handle_name_modal_outcome( | |
| 992 | + | outcome, | |
| 993 | + | state, | |
| 994 | + | |s| s.vfs_modal.show_vfs_create = false, | |
| 995 | + | |s, name| { | |
| 996 | + | s.backend.create_vfs(&name).map_err(|e| format!("{e}"))?; | |
| 997 | + | s.refresh_vfs_list(); | |
| 998 | + | Ok(format!("Created vault: {name}")) | |
| 999 | + | }, | |
| 1000 | + | ); | |
| 1001 | + | } | |
| 1002 | + | ||
| 976 | 1003 | /// Draw the "Rename Vault" modal: text input pre-filled with current name. | |
| 977 | 1004 | pub fn draw_vfs_rename_modal(ctx: &egui::Context, state: &mut BrowserState) { | |
| 978 | 1005 | let error_owned = state.vfs_modal.name_modal_error.clone(); | |
| 979 | - | let outcome = if let Some((_, ref mut name_buf)) = state.vfs_modal.vfs_rename_target { | |
| 980 | - | widgets::name_modal( | |
| 981 | - | ctx, | |
| 982 | - | "Rename Vault", | |
| 983 | - | Some("Vault names can contain spaces."), | |
| 984 | - | "New name:", | |
| 985 | - | name_buf, | |
| 986 | - | "Save", | |
| 987 | - | error_owned.as_deref(), | |
| 1006 | + | let (outcome, vfs_id) = if let Some((id, ref mut name_buf)) = state.vfs_modal.vfs_rename_target | |
| 1007 | + | { | |
| 1008 | + | ( | |
| 1009 | + | widgets::name_modal( | |
| 1010 | + | ctx, | |
| 1011 | + | "Rename Vault", | |
| 1012 | + | Some("Vault names can contain spaces."), | |
| 1013 | + | "New name:", | |
| 1014 | + | name_buf, | |
| 1015 | + | "Save", | |
| 1016 | + | error_owned.as_deref(), | |
| 1017 | + | ), | |
| 1018 | + | id, | |
| 988 | 1019 | ) | |
| 989 | 1020 | } else { | |
| 990 | 1021 | return; | |
| 991 | 1022 | }; | |
| 992 | - | match outcome { | |
| 993 | - | NameModalOutcome::Submitted(new_name) => { | |
| 994 | - | if new_name.is_empty() { | |
| 995 | - | state.vfs_modal.vfs_rename_target = None; | |
| 996 | - | state.vfs_modal.name_modal_error = None; | |
| 997 | - | } else { | |
| 998 | - | let vfs_id = state | |
| 999 | - | .vfs_modal | |
| 1000 | - | .vfs_rename_target | |
| 1001 | - | .as_ref() | |
| 1002 | - | .map(|(id, _)| *id); | |
| 1003 | - | if let Some(vfs_id) = vfs_id { | |
| 1004 | - | match state.backend.rename_vfs(vfs_id, &new_name) { | |
| 1005 | - | Ok(()) => { | |
| 1006 | - | state.refresh_vfs_list(); | |
| 1007 | - | state.status = format!("Renamed vault to: {new_name}"); | |
| 1008 | - | state.vfs_modal.vfs_rename_target = None; | |
| 1009 | - | state.vfs_modal.name_modal_error = None; | |
| 1010 | - | } | |
| 1011 | - | Err(e) => { | |
| 1012 | - | // C-3: keep modal open with the typed name preserved. | |
| 1013 | - | state.vfs_modal.name_modal_error = Some(format!("{e}")); | |
| 1014 | - | } | |
| 1015 | - | } | |
| 1016 | - | } | |
| 1017 | - | } | |
| 1018 | - | } | |
| 1019 | - | NameModalOutcome::Cancelled => { | |
| 1020 | - | state.vfs_modal.vfs_rename_target = None; | |
| 1021 | - | state.vfs_modal.name_modal_error = None; | |
| 1022 | - | } | |
| 1023 | - | NameModalOutcome::None => {} | |
| 1024 | - | } | |
| 1023 | + | handle_name_modal_outcome( | |
| 1024 | + | outcome, | |
| 1025 | + | state, | |
| 1026 | + | |s| s.vfs_modal.vfs_rename_target = None, | |
| 1027 | + | move |s, new_name| { | |
| 1028 | + | s.backend | |
| 1029 | + | .rename_vfs(vfs_id, &new_name) | |
| 1030 | + | .map_err(|e| format!("{e}"))?; | |
| 1031 | + | s.refresh_vfs_list(); | |
| 1032 | + | Ok(format!("Renamed vault to: {new_name}")) | |
| 1033 | + | }, | |
| 1034 | + | ); | |
| 1025 | 1035 | } | |
| 1026 | 1036 | ||
| 1027 | 1037 | /// Draw the "New Folder" modal: text input for folder name. | |
| 1028 | 1038 | pub fn draw_dir_create_modal(ctx: &egui::Context, state: &mut BrowserState) { | |
| 1029 | 1039 | let error_owned = state.vfs_modal.name_modal_error.clone(); | |
| 1030 | - | match widgets::name_modal( | |
| 1040 | + | let outcome = widgets::name_modal( | |
| 1031 | 1041 | ctx, | |
| 1032 | 1042 | "New Folder", | |
| 1033 | 1043 | Some("Folder names cannot contain /"), | |
| @@ -1035,86 +1045,58 @@ pub fn draw_dir_create_modal(ctx: &egui::Context, state: &mut BrowserState) { | |||
| 1035 | 1045 | &mut state.vfs_modal.dir_create_input, | |
| 1036 | 1046 | "Create", | |
| 1037 | 1047 | error_owned.as_deref(), | |
| 1038 | - | ) { | |
| 1039 | - | NameModalOutcome::Submitted(name) => { | |
| 1040 | - | if name.is_empty() { | |
| 1041 | - | state.vfs_modal.show_dir_create = false; | |
| 1042 | - | state.vfs_modal.name_modal_error = None; | |
| 1043 | - | } else if let Some(vfs_id) = state.current_vfs_id() { | |
| 1044 | - | match state | |
| 1045 | - | .backend | |
| 1046 | - | .create_directory(vfs_id, state.nav.current_dir, &name) | |
| 1047 | - | { | |
| 1048 | - | Ok(_) => { | |
| 1049 | - | state.refresh_contents(); | |
| 1050 | - | state.status = format!("Created folder: {name}"); | |
| 1051 | - | state.vfs_modal.show_dir_create = false; | |
| 1052 | - | state.vfs_modal.name_modal_error = None; | |
| 1053 | - | } | |
| 1054 | - | Err(e) => { | |
| 1055 | - | // C-3. | |
| 1056 | - | state.vfs_modal.name_modal_error = Some(format!("{e}")); | |
| 1057 | - | } | |
| 1058 | - | } | |
| 1059 | - | } | |
| 1060 | - | } | |
| 1061 | - | NameModalOutcome::Cancelled => { | |
| 1062 | - | state.vfs_modal.show_dir_create = false; | |
| 1063 | - | state.vfs_modal.name_modal_error = None; | |
| 1064 | - | } | |
| 1065 | - | NameModalOutcome::None => {} | |
| 1066 | - | } | |
| 1048 | + | ); | |
| 1049 | + | let vfs_id = state.current_vfs_id(); | |
| 1050 | + | let current_dir = state.nav.current_dir; | |
| 1051 | + | handle_name_modal_outcome( | |
| 1052 | + | outcome, | |
| 1053 | + | state, | |
| 1054 | + | |s| s.vfs_modal.show_dir_create = false, | |
| 1055 | + | move |s, name| { | |
| 1056 | + | // The New Folder modal is only reachable inside a vault, so this is a | |
| 1057 | + | // defensive guard rather than a real path. | |
| 1058 | + | let vfs_id = vfs_id.ok_or_else(|| "No vault selected".to_string())?; | |
| 1059 | + | s.backend | |
| 1060 | + | .create_directory(vfs_id, current_dir, &name) | |
| 1061 | + | .map_err(|e| format!("{e}"))?; | |
| 1062 | + | s.refresh_contents(); | |
| 1063 | + | Ok(format!("Created folder: {name}")) | |
| 1064 | + | }, | |
| 1065 | + | ); | |
| 1067 | 1066 | } | |
| 1068 | 1067 | ||
| 1069 | 1068 | /// Draw the "Rename Folder" modal: text input pre-filled with current name. | |
| 1070 | 1069 | pub fn draw_dir_rename_modal(ctx: &egui::Context, state: &mut BrowserState) { | |
| 1071 | 1070 | let error_owned = state.vfs_modal.name_modal_error.clone(); | |
| 1072 | - | let outcome = if let Some((_, ref mut name_buf)) = state.vfs_modal.dir_rename_target { | |
| 1073 | - | widgets::name_modal( | |
| 1074 | - | ctx, | |
| 1075 | - | "Rename", | |
| 1076 | - | None, | |
| 1077 | - | "New name:", | |
| 1078 | - | name_buf, | |
| 1079 | - | "Save", | |
| 1080 | - | error_owned.as_deref(), | |
| 1071 | + | let (outcome, node_id) = if let Some((id, ref mut name_buf)) = state.vfs_modal.dir_rename_target | |
| 1072 | + | { | |
| 1073 | + | ( | |
| 1074 | + | widgets::name_modal( | |
| 1075 | + | ctx, | |
| 1076 | + | "Rename", | |
| 1077 | + | None, | |
| 1078 | + | "New name:", | |
| 1079 | + | name_buf, | |
| 1080 | + | "Save", | |
| 1081 | + | error_owned.as_deref(), | |
| 1082 | + | ), | |
| 1083 | + | id, | |
| 1081 | 1084 | ) | |
| 1082 | 1085 | } else { | |
| 1083 | 1086 | return; | |
| 1084 | 1087 | }; | |
| 1085 | - | match outcome { | |
| 1086 | - | NameModalOutcome::Submitted(new_name) => { | |
| 1087 | - | if new_name.is_empty() { | |
| 1088 | - | state.vfs_modal.dir_rename_target = None; | |
| 1089 | - | state.vfs_modal.name_modal_error = None; | |
| 1090 | - | } else { | |
| 1091 | - | let node_id = state | |
| 1092 | - | .vfs_modal | |
| 1093 | - | .dir_rename_target | |
| 1094 | - | .as_ref() | |
| 1095 | - | .map(|(id, _)| *id); | |
| 1096 | - | if let Some(node_id) = node_id { | |
| 1097 | - | match state.backend.rename_node(node_id, &new_name) { | |
| 1098 | - | Ok(()) => { | |
| 1099 | - | state.refresh_contents(); | |
| 1100 | - | state.status = format!("Renamed to: {new_name}"); | |
| 1101 | - | state.vfs_modal.dir_rename_target = None; | |
| 1102 | - | state.vfs_modal.name_modal_error = None; | |
| 1103 | - | } | |
| 1104 | - | Err(e) => { | |
| 1105 | - | // C-3. | |
| 1106 | - | state.vfs_modal.name_modal_error = Some(format!("{e}")); | |
| 1107 | - | } | |
| 1108 | - | } | |
| 1109 | - | } | |
| 1110 | - | } | |
| 1111 | - | } | |
| 1112 | - | NameModalOutcome::Cancelled => { | |
| 1113 | - | state.vfs_modal.dir_rename_target = None; | |
| 1114 | - | state.vfs_modal.name_modal_error = None; | |
| 1115 | - | } | |
| 1116 | - | NameModalOutcome::None => {} | |
| 1117 | - | } | |
| 1088 | + | handle_name_modal_outcome( | |
| 1089 | + | outcome, | |
| 1090 | + | state, | |
| 1091 | + | |s| s.vfs_modal.dir_rename_target = None, | |
| 1092 | + | move |s, new_name| { | |
| 1093 | + | s.backend | |
| 1094 | + | .rename_node(node_id, &new_name) | |
| 1095 | + | .map_err(|e| format!("{e}"))?; | |
| 1096 | + | s.refresh_contents(); | |
| 1097 | + | Ok(format!("Renamed to: {new_name}")) | |
| 1098 | + | }, | |
| 1099 | + | ); | |
| 1118 | 1100 | } | |
| 1119 | 1101 | ||
| 1120 | 1102 | #[cfg(test)] |