max / audiofiles
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
5 files changed,
+48 insertions,
-87 deletions
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | edition.workspace = true | |
| 6 | 6 | ||
| 7 | 7 | [features] | |
| 8 | - | default = ["device-profiles"] | |
| 8 | + | # `quasi` is on by default as of 2026-08-22, when the loose-files warning became | |
| 9 | + | # the first screen to serve from the description rather than sit beside one. Off, | |
| 10 | + | # that screen and every one flipped after it is simply missing. The feature | |
| 11 | + | # itself goes when the last flip lands; until then it is a switch with one | |
| 12 | + | # position that is still spelled. | |
| 13 | + | default = ["device-profiles", "quasi"] | |
| 9 | 14 | device-profiles = ["dep:audiofiles-rhai", "dep:rayon"] | |
| 10 | 15 | # The described screens, off by default. On, `crate::quasi` compiles and the | |
| 11 | 16 | # shipped egui panels in `crate::ui` are untouched; off, none of it is built. |
| @@ -154,7 +154,7 @@ | |||
| 154 | 154 | overlays::draw_dir_rename_modal(ctx, state); | |
| 155 | 155 | } | |
| 156 | 156 | if state.loose_files.show_loose_files_warning { | |
| 157 | - | overlays::draw_loose_files_warning(ctx, state); | |
| 157 | + | crate::quasi::panel::draw_integrity(ctx, state); | |
| 158 | 158 | } | |
| 159 | 159 | if state.import_wf.pending_import_preflight.is_some() { | |
| 160 | 160 | overlays::draw_import_preflight(ctx, state); |
| @@ -59,6 +59,7 @@ | |||
| 59 | 59 | queue: Option<Runtime>, | |
| 60 | 60 | sweep: Option<Runtime>, | |
| 61 | 61 | filters: Option<Runtime>, | |
| 62 | + | integrity: Option<Runtime>, | |
| 62 | 63 | /// Whether the described main window is open. | |
| 63 | 64 | pub show_shell: bool, | |
| 64 | 65 | /// Whether the described detail panel is open. | |
| @@ -453,6 +454,41 @@ | |||
| 453 | 454 | } | |
| 454 | 455 | } | |
| 455 | 456 | ||
| 457 | + | /// Draw the loose-files warning, and act on whatever was pressed. | |
| 458 | + | /// | |
| 459 | + | /// The first screen to serve rather than sit beside one, 2026-08-22. What the | |
| 460 | + | /// shipped overlay did that this does not is stop you: it drew as a modal over | |
| 461 | + | /// a dimmed app, and a described screen cannot raise itself, so the count is | |
| 462 | + | /// said in the status band and this is one act away from it. `integrity`'s | |
| 463 | + | /// header argues that difference; it is recorded rather than smoothed over. | |
| 464 | + | /// | |
| 465 | + | /// Refreshed every frame, because the worker that re-checks the vault moves the | |
| 466 | + | /// count with nothing pressed. | |
| 467 | + | pub fn draw_integrity(ctx: &egui::Context, state: &mut BrowserState) { | |
| 468 | + | let intents = RefCell::new(Vec::new()); | |
| 469 | + | let mut runtime = state.described.integrity.take(); | |
| 470 | + | let host = Host { | |
| 471 | + | state, | |
| 472 | + | sync: None, | |
| 473 | + | themes: themes(), | |
| 474 | + | intents: &intents, | |
| 475 | + | }; | |
| 476 | + | let closed = window( | |
| 477 | + | ctx, | |
| 478 | + | "Loose-files mode warning", | |
| 479 | + | &mut runtime, | |
| 480 | + | &host, | |
| 481 | + | "/library/loose-files", | |
| 482 | + | true, | |
| 483 | + | ); | |
| 484 | + | state.described.integrity = runtime; | |
| 485 | + | apply(ctx, state, None, intents.into_inner()); | |
| 486 | + | if closed { | |
| 487 | + | state.described.integrity = None; | |
| 488 | + | state.dismiss_loose_files_warning(); | |
| 489 | + | } | |
| 490 | + | } | |
| 491 | + | ||
| 456 | 492 | /// Do what a described screen asked the app to do to itself. | |
| 457 | 493 | /// | |
| 458 | 494 | /// **The frame boundary.** A route holds `&BrowserState` and cannot select a |
| @@ -466,6 +466,11 @@ | |||
| 466 | 466 | /// record of what that flip changed. There is deliberately no "ignore whatever | |
| 467 | 467 | /// differs" option: an unexplained difference is the thing this file exists to | |
| 468 | 468 | /// find. | |
| 469 | + | /// | |
| 470 | + | /// One allowance recurs and is worth knowing before it surprises you: a shipped | |
| 471 | + | /// modal drawn in an `egui::Window` announces a button carrying the window's own | |
| 472 | + | /// name, because that is its title bar's collapsing control. It is chrome, in | |
| 473 | + | /// the same class as a `CollapsingHeader`, and a modal test drops it by name. | |
| 469 | 474 | #[derive(Debug, Clone, Default)] | |
| 470 | 475 | pub(super) struct Parity { | |
| 471 | 476 | dropped: Vec<String>, |
| @@ -453,91 +453,6 @@ | |||
| 453 | 453 | } | |
| 454 | 454 | } | |
| 455 | 455 | ||
| 456 | - | /// Draw the loose-files mode integrity warning overlay. | |
| 457 | - | /// | |
| 458 | - | /// Three-button layout (C-2): Locate (recover sources), Purge (delete the | |
| 459 | - | /// registry entries plus their tags / analysis / history), Cancel (dismiss | |
| 460 | - | /// without acting). Locate is the recovery path that the prior two-button | |
| 461 | - | /// version was missing entirely. | |
| 462 | - | pub fn draw_loose_files_warning(ctx: &egui::Context, state: &mut BrowserState) { | |
| 463 | - | let count = state.loose_files.loose_files_missing_count; | |
| 464 | - | if count == 0 { | |
| 465 | - | return; | |
| 466 | - | } | |
| 467 | - | ||
| 468 | - | let prompt = format!( | |
| 469 | - | "{count} sample{} in this vault {} missing source {}.", | |
| 470 | - | if count == 1 { "" } else { "s" }, | |
| 471 | - | if count == 1 { "has a" } else { "have" }, | |
| 472 | - | if count == 1 { "file" } else { "files" }, | |
| 473 | - | ); | |
| 474 | - | ||
| 475 | - | let mut action: Option<LooseFilesAction> = None; | |
| 476 | - | widgets::modal_window(ctx, "Loose-files mode warning", false, Some(480.0), |ui| { | |
| 477 | - | ui.label(egui::RichText::new(&prompt).strong()); | |
| 478 | - | ui.add_space(theme::space::bound()); | |
| 479 | - | ui.label( | |
| 480 | - | "The original files may have been moved or deleted. These samples \ | |
| 481 | - | cannot be played or exported until the files are restored.", | |
| 482 | - | ); | |
| 483 | - | ui.add_space(theme::space::bound()); | |
| 484 | - | // C-2: name the blast radius of Purge explicitly. Tags, analysis, and | |
| 485 | - | // history are the data the user has invested time in, they should | |
| 486 | - | // see what Purge takes before reaching for it. | |
| 487 | - | ui.label( | |
| 488 | - | egui::RichText::new( | |
| 489 | - | "Tags, analysis results, and history for these samples will \ | |
| 490 | - | be permanently deleted by Purge.", | |
| 491 | - | ) | |
| 492 | - | .small() | |
| 493 | - | .color(theme::warning()), | |
| 494 | - | ); | |
| 495 | - | ui.add_space(theme::space::peer()); | |
| 496 | - | ui.horizontal(|ui| { | |
| 497 | - | if ui | |
| 498 | - | .button("Cancel") | |
| 499 | - | .on_hover_text("Dismiss without acting") | |
| 500 | - | .clicked() | |
| 501 | - | { | |
| 502 | - | action = Some(LooseFilesAction::Cancel); | |
| 503 | - | } | |
| 504 | - | if ui | |
| 505 | - | .button("Locate missing files...") | |
| 506 | - | .on_hover_text( | |
| 507 | - | "Pick a folder; audiofiles will hash-verify and re-point any \ | |
| 508 | - | samples that match. Tags and analysis are preserved.", | |
| 509 | - | ) | |
| 510 | - | .clicked() | |
| 511 | - | { | |
| 512 | - | action = Some(LooseFilesAction::Locate); | |
| 513 | - | } | |
| 514 | - | if widgets::danger_button(ui, "Purge").clicked() { | |
| 515 | - | action = Some(LooseFilesAction::Purge); | |
| 516 | - | } | |
| 517 | - | }); | |
| 518 | - | }); | |
| 519 | - | ||
| 520 | - | match action { | |
| 521 | - | Some(LooseFilesAction::Cancel) => state.dismiss_loose_files_warning(), | |
| 522 | - | Some(LooseFilesAction::Purge) => state.purge_missing_loose_files(), | |
| 523 | - | Some(LooseFilesAction::Locate) => { | |
| 524 | - | state | |
| 525 | - | .dialogs | |
| 526 | - | .pick_folder("Locate missing sample files", |s, p| { | |
| 527 | - | s.locate_missing_loose_files(&p); | |
| 528 | - | }); | |
| 529 | - | } | |
| 530 | - | None => {} | |
| 531 | - | } | |
| 532 | - | } | |
| 533 | - | ||
| 534 | - | /// Outcome of the loose-files-warning dialog (C-2). | |
| 535 | - | enum LooseFilesAction { | |
| 536 | - | Cancel, | |
| 537 | - | Locate, | |
| 538 | - | Purge, | |
| 539 | - | } | |
| 540 | - | ||
| 541 | 456 | /// Draw the active bulk modal (tag, move, or rename). | |
| 542 | 457 | pub fn draw_bulk_modal(ctx: &egui::Context, state: &mut BrowserState) { | |
| 543 | 458 | let modal_kind = match &state.bulk_modal { |