cbd-web: drop the unreachable capture-delete confirmation
delete_needs_confirmation only ever returned true for /captures paths, and /captures stopped existing when saved queues, bookmarks, and captures folded into the single /crabidy provider. So the y/N dialog could not open, the web client already deleted immediately, and its comment claiming to mirror the TUI described an arrangement neither client had. Removed rather than re-pointed at /crabidy: a delete there drops the metadata toml only, never the shared store audio, which survives and resurfaces under /orphans — so there is little to guard. Both clients now behave the same, which is what the docs describe. Gone with it: the Dialog::ConfirmDelete variant, the ConfirmDialog component and its keyboard handler, the test, and the two CSS rules only that dialog wore (.danger-dialog and the solid .danger button; .ghost.danger stays, three row actions still use it). Verified: clippy clean for wasm32 (where `mod app` actually compiles) and native, 12 cbd-web tests pass, and the trunk bundle builds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ba4775b0bb
commit
d630c9e550
|
|
@ -15,8 +15,8 @@ use leptos::task::spawn_local;
|
|||
use crate::keymap::{self, Action};
|
||||
use crate::rpc::Rpc;
|
||||
use crate::state::{
|
||||
delete_needs_confirmation, format_seconds, is_cacheable, track_label, CaptureBoard, Dialog,
|
||||
Focus, LibraryPane, NamePurpose, QueueCursor, UiItemKind,
|
||||
format_seconds, is_cacheable, track_label, CaptureBoard, Dialog, Focus, LibraryPane,
|
||||
NamePurpose, QueueCursor, UiItemKind,
|
||||
};
|
||||
|
||||
const VOLUME_STEP: f32 = 0.1;
|
||||
|
|
@ -388,16 +388,16 @@ impl Store {
|
|||
}
|
||||
}
|
||||
Action::LibraryDeleteNode => {
|
||||
if let Some((path, title)) =
|
||||
// Deleting under `/crabidy` drops the metadata toml only; the
|
||||
// shared store audio survives and resurfaces under
|
||||
// `/orphans`, so there is nothing to confirm — same as the
|
||||
// TUI.
|
||||
if let Some((path, _title)) =
|
||||
self.library.with_untracked(LibraryPane::selected_deletable)
|
||||
{
|
||||
if delete_needs_confirmation(&path) {
|
||||
self.dialog.set(Some(Dialog::ConfirmDelete { path, title }));
|
||||
} else {
|
||||
self.delete_node(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::LibraryCaptureNode => {
|
||||
if let Some((path, title)) =
|
||||
self.library.with_untracked(LibraryPane::selected_queueable)
|
||||
|
|
@ -1122,9 +1122,6 @@ fn Dialogs(store: Store) -> impl IntoView {
|
|||
Dialog::Name { purpose, buffer } => {
|
||||
view! { <NameDialog store=store purpose=purpose buffer=buffer /> }.into_any()
|
||||
}
|
||||
Dialog::ConfirmDelete { path, title } => {
|
||||
view! { <ConfirmDialog store=store path=path title=title /> }.into_any()
|
||||
}
|
||||
Dialog::Login => view! { <LoginDialog store=store /> }.into_any(),
|
||||
Dialog::Help => view! { <HelpOverlay store=store /> }.into_any(),
|
||||
})
|
||||
|
|
@ -1166,48 +1163,6 @@ fn NameDialog(store: Store, purpose: NamePurpose, buffer: String) -> impl IntoVi
|
|||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ConfirmDialog(store: Store, path: String, title: String) -> impl IntoView {
|
||||
let confirm_path = path.clone();
|
||||
let confirm = move |_| {
|
||||
store.dialog.set(None);
|
||||
store.delete_node(confirm_path.clone());
|
||||
};
|
||||
// y/N without leaving the keyboard, like the TUI: the overlay grabs
|
||||
// the keys while it is open.
|
||||
let key_path = path;
|
||||
let handle = window_event_listener(leptos::ev::keydown, move |ev| {
|
||||
if !matches!(
|
||||
store.dialog.get_untracked(),
|
||||
Some(Dialog::ConfirmDelete { .. })
|
||||
) {
|
||||
return;
|
||||
}
|
||||
ev.prevent_default();
|
||||
store.dialog.set(None);
|
||||
if matches!(ev.key().as_str(), "y" | "Y") {
|
||||
store.delete_node(key_path.clone());
|
||||
}
|
||||
});
|
||||
on_cleanup(move || handle.remove());
|
||||
view! {
|
||||
<div class="overlay" on:click=move |_| store.dialog.set(None)>
|
||||
<div class="dialog danger-dialog" on:click=|ev| ev.stop_propagation()>
|
||||
<p>
|
||||
"Delete "<strong>{title}</strong>
|
||||
" from disk (downloaded audio included)?"
|
||||
</p>
|
||||
<div class="dialog-actions">
|
||||
<button class="ghost" on:click=move |_| store.dialog.set(None)>
|
||||
"cancel (N)"
|
||||
</button>
|
||||
<button class="danger" on:click=confirm>"delete (y)"</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn LoginDialog(store: Store) -> impl IntoView {
|
||||
let user = RwSignal::new(load_pref("user").unwrap_or_default());
|
||||
|
|
|
|||
|
|
@ -52,8 +52,6 @@ pub enum Dialog {
|
|||
purpose: NamePurpose,
|
||||
buffer: String,
|
||||
},
|
||||
/// The capture-delete confirmation (architecture/capture-deletion.md).
|
||||
ConfirmDelete { path: String, title: String },
|
||||
/// Credentials form. Shown on `UNAUTHENTICATED` responses (creds
|
||||
/// required), and proactively — but dismissible — on first connect
|
||||
/// when the server reports auth is enabled and we hold none.
|
||||
|
|
@ -62,13 +60,6 @@ pub enum Dialog {
|
|||
Help,
|
||||
}
|
||||
|
||||
/// Whether deleting `path` needs the y/N confirmation — same rule as
|
||||
/// the TUI: captures hold downloaded audio, everything else deletable
|
||||
/// is cheap to recreate.
|
||||
pub fn delete_needs_confirmation(path: &str) -> bool {
|
||||
path == "/captures" || path.starts_with("/captures/")
|
||||
}
|
||||
|
||||
/// Whether a library listing may be cached client-side — same rule as
|
||||
/// the TUI (`cbd-tui/src/rpc.rs`): server-side folder providers mutate
|
||||
/// behind the client's back and are cheap to re-list. `/crabidy` (saves and
|
||||
|
|
@ -467,15 +458,6 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capture_deletes_need_confirmation_cheap_deletes_do_not() {
|
||||
assert!(delete_needs_confirmation("/captures/mix"));
|
||||
assert!(delete_needs_confirmation("/captures/mix/a.cbd-track.toml"));
|
||||
assert!(!delete_needs_confirmation("/queues/roadtrip"));
|
||||
assert!(!delete_needs_confirmation("/tidal/search/abba"));
|
||||
assert!(!delete_needs_confirmation("/capturesque"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mutable_roots_are_never_cacheable() {
|
||||
for path in [
|
||||
|
|
|
|||
|
|
@ -76,11 +76,6 @@ button {
|
|||
}
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background: var(--danger);
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
&.ghost.danger {
|
||||
background: transparent;
|
||||
color: var(--danger);
|
||||
|
|
@ -395,10 +390,6 @@ input {
|
|||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
&.danger-dialog {
|
||||
border-inline-start: 4px solid var(--danger);
|
||||
}
|
||||
}
|
||||
|
||||
.help {
|
||||
|
|
|
|||
Loading…
Reference in New Issue