max / audiofiles
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+18 insertions,
-192 deletions
| @@ -1,11 +1,26 @@ | |||
| 1 | - | //! Context menus and drag-out handlers extracted from file_list.rs. | |
| 1 | + | //! What is left of the file list's context menus, plus the drag-out handler. | |
| 2 | + | //! | |
| 3 | + | //! **Three of the four things in here have no caller**, and that is a regression | |
| 4 | + | //! rather than dead code: the file-list flip (`49b7429`) replaced the shipped | |
| 5 | + | //! list with `quasi::files`, which describes the *row* menu through | |
| 6 | + | //! [`Cells::menu`](quasi_router::Cells) and does not describe the other two | |
| 7 | + | //! menus or the drag at all. So the selection menu, the empty-space menu and | |
| 8 | + | //! dragging a sample out to a DAW are all unreachable in the running app. | |
| 9 | + | //! | |
| 10 | + | //! They are kept rather than deleted, because deleting them is losing the | |
| 11 | + | //! features quietly and each is waiting on something specific: | |
| 12 | + | //! `quasi:vocabulary:anchored-menu` for the two menus (a menu over a selection | |
| 13 | + | //! or over a surface has no container in the vocabulary), and a host-input | |
| 14 | + | //! answer for the drag, which `quasi::files` records as deliberately undescribed. | |
| 15 | + | //! Filed as audiofiles `edd4d1d8`. | |
| 16 | + | //! | |
| 17 | + | //! `draw_context_menu` was the fourth and is deleted (2026-08-25): its | |
| 18 | + | //! replacement ships, so it was the one thing in here that really was dead. | |
| 2 | 19 | ||
| 3 | 20 | use egui; | |
| 4 | 21 | ||
| 5 | 22 | use crate::state::BrowserState; | |
| 6 | - | use audiofiles_core::vfs::NodeType; | |
| 7 | 23 | ||
| 8 | - | use super::theme; | |
| 9 | 24 | use super::widgets; | |
| 10 | 25 | ||
| 11 | 26 | #[cfg(any(target_os = "macos", target_os = "windows"))] | |
| @@ -58,195 +73,6 @@ | |||
| 58 | 73 | } | |
| 59 | 74 | } | |
| 60 | 75 | ||
| 61 | - | /// Draw the right-click context menu for a single item. | |
| 62 | - | /// Branches on node type: samples get Preview/Copy Path/Delete, | |
| 63 | - | /// directories get Open/Delete. | |
| 64 | - | pub fn draw_context_menu( | |
| 65 | - | ui: &mut egui::Ui, | |
| 66 | - | state: &mut BrowserState, | |
| 67 | - | row_idx: usize, | |
| 68 | - | node: &audiofiles_core::vfs::VfsNodeWithAnalysis, | |
| 69 | - | sync_manager: Option<&audiofiles_sync::SyncManager>, | |
| 70 | - | ) { | |
| 71 | - | match node.node.node_type { | |
| 72 | - | NodeType::Sample => { | |
| 73 | - | if node.cloud_only { | |
| 74 | - | ui.label( | |
| 75 | - | egui::RichText::new("Cloud-only sample") | |
| 76 | - | .color(theme::content_muted()) | |
| 77 | - | .italics(), | |
| 78 | - | ); | |
| 79 | - | // Targeted download for the row under the cursor. Falls back | |
| 80 | - | // gracefully when sync isn't configured (CLAP plugin, dev | |
| 81 | - | // builds without an embedded API key) by hiding the item. | |
| 82 | - | if let Some(sync) = sync_manager | |
| 83 | - | && let Some(hash) = &node.node.sample_hash | |
| 84 | - | && ui | |
| 85 | - | .button("Download") | |
| 86 | - | .on_hover_text("Fetch this sample from the cloud to local storage") | |
| 87 | - | .clicked() | |
| 88 | - | { | |
| 89 | - | let hash = hash.to_string(); | |
| 90 | - | if sync.download_sample(&hash) { | |
| 91 | - | state.status = format!("Downloading {}...", node.node.name); | |
| 92 | - | } else { | |
| 93 | - | state.status = "Sync not ready, open the Sync panel first".to_string(); | |
| 94 | - | } | |
| 95 | - | ui.close(); | |
| 96 | - | } | |
| 97 | - | ui.separator(); | |
| 98 | - | } | |
| 99 | - | if !node.cloud_only && ui.button("Preview").clicked() { | |
| 100 | - | if let Some(hash) = &node.node.sample_hash { | |
| 101 | - | let hash = hash.clone(); | |
| 102 | - | state.trigger_preview(&hash); | |
| 103 | - | } | |
| 104 | - | ui.close(); | |
| 105 | - | } | |
| 106 | - | if ui.button("Copy Path").clicked() { | |
| 107 | - | if let Some(path) = state.selected_sample_path() { | |
| 108 | - | state.status = format!("Copied: {path}"); | |
| 109 | - | ui.ctx().copy_text(path); | |
| 110 | - | } | |
| 111 | - | ui.close(); | |
| 112 | - | } | |
| 113 | - | // M-6: one-click jump to the file in the system file manager. The | |
| 114 | - | // label and the spawning both live at the top of this file now, so | |
| 115 | - | // the described file list offers the same act under the same name. | |
| 116 | - | if !node.cloud_only && ui.button(reveal_label()).clicked() { | |
| 117 | - | if let Some(path) = state.selected_sample_path() { | |
| 118 | - | reveal(&path); | |
| 119 | - | } | |
| 120 | - | ui.close(); | |
| 121 | - | } | |
| 122 | - | if ui.button("Find Similar (Shift+F)").clicked() { | |
| 123 | - | if let Some(hash) = &node.node.sample_hash { | |
| 124 | - | let hash = hash.clone(); | |
| 125 | - | state.find_similar(&hash); | |
| 126 | - | } | |
| 127 | - | ui.close(); | |
| 128 | - | } | |
| 129 | - | if ui.button("Find Duplicates (Shift+D)").clicked() { | |
| 130 | - | if let Some(hash) = &node.node.sample_hash { | |
| 131 | - | let hash = hash.clone(); | |
| 132 | - | state.find_near_duplicates(&hash); | |
| 133 | - | } | |
| 134 | - | ui.close(); | |
| 135 | - | } | |
| 136 | - | // Add to Collection submenu | |
| 137 | - | if let Some(hash) = &node.node.sample_hash { | |
| 138 | - | let hash_clone = hash.clone(); | |
| 139 | - | let collections = state.collections_ui.collections.clone(); | |
| 140 | - | let is_in_collection = state.collections_ui.active_collection.is_some(); | |
| 141 | - | if !collections.is_empty() { | |
| 142 | - | ui.menu_button("Add to Collection", |ui| { | |
| 143 | - | for coll in collections.iter() { | |
| 144 | - | if ui.button(&coll.name).clicked() { | |
| 145 | - | let _ = state.backend.add_to_collection(coll.id, &hash_clone); | |
| 146 | - | state.refresh_collections(); | |
| 147 | - | state.status = format!("Added to {}", coll.name); | |
| 148 | - | ui.close(); | |
| 149 | - | } | |
| 150 | - | } | |
| 151 | - | }); | |
| 152 | - | } | |
| 153 | - | if is_in_collection | |
| 154 | - | && let Some(active_id) = state.collections_ui.active_collection | |
| 155 | - | && widgets::danger_button(ui, "Remove from Collection").clicked() | |
| 156 | - | { | |
| 157 | - | let _ = state.backend.remove_from_collection(active_id, &hash_clone); | |
| 158 | - | state.refresh_collections(); | |
| 159 | - | state.activate_collection(active_id); | |
| 160 | - | ui.close(); | |
| 161 | - | } | |
| 162 | - | } | |
| 163 | - | if !node.cloud_only { | |
| 164 | - | if let Some(hash) = &node.node.sample_hash { | |
| 165 | - | let hash_clone = hash.clone(); | |
| 166 | - | if ui.button("Edit... (E)").clicked() { | |
| 167 | - | state.open_edit_window(&hash_clone); | |
| 168 | - | ui.close(); | |
| 169 | - | } | |
| 170 | - | } | |
| 171 | - | if ui.button("Play as Instrument").clicked() { | |
| 172 | - | if let Some(hash) = &node.node.sample_hash { | |
| 173 | - | let hash = hash.clone(); | |
| 174 | - | let name = node.node.name.clone(); | |
| 175 | - | state.load_chromatic_sample(&hash); | |
| 176 | - | state.preview.instrument_visible = true; | |
| 177 | - | state.preview.show_midi_window = true; | |
| 178 | - | state.status = format!("Instrument: {name}"); | |
| 179 | - | } | |
| 180 | - | ui.close(); | |
| 181 | - | } | |
| 182 | - | if ui.button("Export...").clicked() { | |
| 183 | - | state.nav.selection.set_single(row_idx); | |
| 184 | - | state.start_export_flow(Some(vec![node.node.id])); | |
| 185 | - | ui.close(); | |
| 186 | - | } | |
| 187 | - | // M-7: single-row Re-analyze parity with the multi-row menu. | |
| 188 | - | // Reuses ReanalyzeOverwrite with a one-element vec so the | |
| 189 | - | // backend path matches the bulk case exactly. | |
| 190 | - | if ui | |
| 191 | - | .button("Re-analyze...") | |
| 192 | - | .on_hover_text("Run analysis again on this sample") | |
| 193 | - | .clicked() | |
| 194 | - | { | |
| 195 | - | if let Some(hash) = &node.node.sample_hash | |
| 196 | - | && let Ok(ext) = state.backend.sample_extension(hash) | |
| 197 | - | { | |
| 198 | - | let hashes = vec![(hash.to_string(), ext)]; | |
| 199 | - | let has_existing = node.bpm.is_some() || node.musical_key.is_some(); | |
| 200 | - | if has_existing { | |
| 201 | - | state.overlay.pending_confirm = | |
| 202 | - | Some(crate::state::ConfirmAction::ReanalyzeOverwrite { | |
| 203 | - | sample_hashes: hashes, | |
| 204 | - | overwrite_count: 1, | |
| 205 | - | }); | |
| 206 | - | } else { | |
| 207 | - | state.start_analysis_flow(hashes); | |
| 208 | - | } | |
| 209 | - | } | |
| 210 | - | ui.close(); | |
| 211 | - | } | |
| 212 | - | } | |
| 213 | - | ui.separator(); | |
| 214 | - | if widgets::danger_button(ui, "Delete").clicked() { | |
| 215 | - | state.nav.selection.set_single(row_idx); | |
| 216 | - | state.confirm_delete_selected(); | |
| 217 | - | ui.close(); | |
| 218 | - | } | |
| 219 | - | } | |
| 220 | - | NodeType::Directory => { | |
| 221 | - | if ui.button("Open").clicked() { | |
| 222 | - | state.nav.selection.set_single(row_idx); | |
| 223 | - | state.enter_directory(); | |
| 224 | - | ui.close(); | |
| 225 | - | } | |
| 226 | - | if ui.button("New Folder").clicked() { | |
| 227 | - | state.vfs_modal.show_dir_create = true; | |
| 228 | - | state.vfs_modal.dir_create_input.clear(); | |
| 229 | - | ui.close(); | |
| 230 | - | } | |
| 231 | - | if ui.button("Rename").clicked() { | |
| 232 | - | state.vfs_modal.dir_rename_target = Some((node.node.id, node.node.name.clone())); | |
| 233 | - | ui.close(); | |
| 234 | - | } | |
| 235 | - | if ui.button("Export...").clicked() { | |
| 236 | - | state.nav.selection.set_single(row_idx); | |
| 237 | - | state.start_export_flow(Some(vec![node.node.id])); | |
| 238 | - | ui.close(); | |
| 239 | - | } | |
| 240 | - | ui.separator(); | |
| 241 | - | if widgets::danger_button(ui, "Delete").clicked() { | |
| 242 | - | state.nav.selection.set_single(row_idx); | |
| 243 | - | state.confirm_delete_selected(); | |
| 244 | - | ui.close(); | |
| 245 | - | } | |
| 246 | - | } | |
| 247 | - | } | |
| 248 | - | } | |
| 249 | - | ||
| 250 | 76 | /// Context menu when multiple items are selected. | |
| 251 | 77 | pub fn draw_multi_context_menu(ui: &mut egui::Ui, state: &mut BrowserState) { | |
| 252 | 78 | let count = state.nav.selection.count(); |