Skip to main content

max / audiofiles

5.2 KB · 128 lines History Blame Raw
1 //! The loose-files warning, described: what is missing, and the three things
2 //! that can be done about it.
3 //!
4 //! # The second consumer of the unprompted-overlay finding
5 //!
6 //! [`importing`](super::importing)'s header files it and this is the other half:
7 //! nobody asks for this modal either. `check_loose_files_integrity` runs after a
8 //! vault loads, counts the samples whose source file is gone, and the shipped
9 //! overlay appears over whatever the user was doing.
10 //!
11 //! The described side cannot raise itself, so the fact is said where it is
12 //! known — the status band, which already reports what the app is doing — and the
13 //! overlay is one act away from it. See [`shell`](super::shell)'s `foot`. That is
14 //! a real behaviour difference from the shipped app and it is recorded rather
15 //! than smoothed over: a warning you can ignore is not a warning that stopped
16 //! you. Which of the two is right is a question for the eyeball, and neither is
17 //! sayable today.
18 //!
19 //! # THE FINDING, fourth consumer: Locate is a host act
20 //!
21 //! `Locate missing files...` opens a native folder picker
22 //! (`state.dialogs.pick_folder`), and `quasi:vocabulary:host-save-location` is
23 //! the gap that nothing describes one. Same shape as the export destination, the
24 //! import source and the theme export before it. It is an ordinary act here and
25 //! the host does what only a host can, which is the same workaround those three
26 //! took.
27 //!
28 //! # Purge says what it takes, on the control
29 //!
30 //! The shipped modal draws the blast radius as a warning-toned line above the
31 //! button row: tags, analysis and history go. `Act::confirm` carries it now, so
32 //! the sentence is attached to the thing that does it rather than sitting near
33 //! it, which is `524a63fe`'s argument and the third `ConfirmAction`-shaped thing
34 //! this port has replaced with a builder method.
35
36 use quasi_router::layout::{Notice, Tone};
37 use quasi_router::{
38 Act, Action, Node, Outcome, RegionKind, Request, Response, RouteError, Router, Screen, Slot,
39 };
40
41 use super::Panels;
42
43 /// The region the warning answers into.
44 const BODY: &str = "loose-files";
45
46 /// Where an answered warning goes.
47 const BACK: &str = "/";
48
49 /// What Purge takes with it, said on the control that does it.
50 const BLAST: &str =
51 "Tags, analysis results, and history for these samples will be permanently deleted. Purge?";
52
53 /// Register the warning's routes.
54 pub fn routes(router: Router<Panels<'_>>) -> Router<Panels<'_>> {
55 router
56 .get("/library/loose-files", screen)
57 .post("/library/loose-files/dismiss", dismiss)
58 .post("/library/loose-files/locate", locate)
59 .post("/library/loose-files/purge", purge)
60 }
61
62 /// `GET /library/loose-files`
63 fn screen(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> {
64 let missing = state.integrity.missing();
65 if missing == 0 {
66 return Err(RouteError::not_found("nothing is missing"));
67 }
68
69 let body = Slot::new(BODY, RegionKind::Pane)
70 .with(Node::page("Loose-files mode warning"))
71 .with(Node::Notice {
72 kind: Notice::Banner,
73 tone: Tone::Warning,
74 text: format!(
75 "{missing} sample{} in this vault {} missing source {}.",
76 if missing == 1 { "" } else { "s" },
77 if missing == 1 { "has a" } else { "have" },
78 if missing == 1 { "file" } else { "files" },
79 ),
80 })
81 .with(Node::text(
82 "The original files may have been moved or deleted. These samples cannot be played or exported until the files are restored.",
83 ))
84 .with(Node::Act(
85 Act::new("Locate missing files", Action::post("/library/loose-files/locate")),
86 ))
87 .with(Node::Act(
88 Act::new("Purge", Action::post("/library/loose-files/purge"))
89 .tone(Tone::Danger)
90 .confirm(BLAST),
91 ))
92 .with(Node::Act(
93 Act::new("Cancel", Action::post("/library/loose-files/dismiss")).key("esc"),
94 ));
95
96 Ok(Response::from(Outcome::Over(
97 Screen::sidebar_content("Loose-files mode warning").with(body),
98 )))
99 }
100
101 /// `POST /library/loose-files/dismiss`
102 fn dismiss(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> {
103 state.integrity.dismiss();
104 Ok(Response::from(Outcome::Goto(Action::get(BACK))))
105 }
106
107 /// `POST /library/loose-files/locate`
108 ///
109 /// The answer leaves before the picker opens, and that is honest rather than
110 /// hurried: the host's dialog runs on its own and lands in the app whenever the
111 /// user is done with it, so there is nothing for this screen to wait for. See
112 /// the module header on why a picker is not described at all.
113 fn locate(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> {
114 state.integrity.locate();
115 Ok(Response::from(Outcome::Goto(Action::get(BACK))))
116 }
117
118 /// `POST /library/loose-files/purge`
119 fn purge(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> {
120 let missing = state.integrity.missing();
121 if missing == 0 {
122 return Err(RouteError::not_found("nothing is missing"));
123 }
124 state.integrity.purge();
125 Ok(Response::from(Outcome::Goto(Action::get(BACK)))
126 .toast(Tone::Warning, format!("Purging {missing} samples.")))
127 }
128