| 45 |
45 |
|
//! [`Act::confirm`]: quasi_router::Act::confirm
|
| 46 |
46 |
|
//! [`Trash::retain_days`]: super::Trash::retain_days
|
| 47 |
47 |
|
|
| 48 |
|
- |
use quasi_router::layout::Tone;
|
| 49 |
|
- |
use quasi_router::{Act, Action, Node, Request, Response, RouteError, Router, Row, Slot, Tag};
|
|
48 |
+ |
use quasi_declare::declare;
|
|
49 |
+ |
use quasi_router::{Request, Response, RouteError, Router, Tag};
|
| 50 |
50 |
|
|
| 51 |
51 |
|
use super::Panels;
|
| 52 |
52 |
|
|
| 88 |
88 |
|
Ok(super::settings::screen(state)?.into())
|
| 89 |
89 |
|
}
|
| 90 |
90 |
|
|
| 91 |
|
- |
/// The whole section, added to the settings body.
|
| 92 |
|
- |
pub(super) fn section(body: Slot, state: &Panels<'_>) -> Slot {
|
|
91 |
+ |
/// What the section draws, read off the app once.
|
|
92 |
+ |
pub(super) struct Bin {
|
|
93 |
+ |
/// How long a deleted sample is kept, in days.
|
|
94 |
+ |
days: i64,
|
|
95 |
+ |
/// "day" or "days", to agree with `days` in the sentence above the list.
|
|
96 |
+ |
unit: &'static str,
|
|
97 |
+ |
/// What is in there now, most recently deleted first.
|
|
98 |
+ |
gone: Vec<Gone>,
|
|
99 |
+ |
}
|
|
100 |
+ |
|
|
101 |
+ |
/// One tombstoned sample, in the words the row uses.
|
|
102 |
+ |
struct Gone {
|
|
103 |
+ |
/// Its content address, which is what both acts carry.
|
|
104 |
+ |
hash: String,
|
|
105 |
+ |
/// The name a reader sees.
|
|
106 |
+ |
filename: String,
|
|
107 |
+ |
/// Size and age, on the line under the name.
|
|
108 |
+ |
meta: String,
|
|
109 |
+ |
/// The head of the hash, when the hash has a head worth showing.
|
|
110 |
+ |
short: Option<String>,
|
|
111 |
+ |
}
|
|
112 |
+ |
|
|
113 |
+ |
/// What the section draws, read off the app.
|
|
114 |
+ |
pub(super) fn read(state: &Panels<'_>) -> Bin {
|
| 93 |
115 |
|
let days = state.trash.retain_days();
|
| 94 |
|
- |
let body = body
|
| 95 |
|
- |
.with(Node::section("Trash"))
|
| 96 |
|
- |
.with(Node::text(format!(
|
| 97 |
|
- |
"Deleted samples are kept here for {days} {}, then removed permanently. Restoring brings a sample back on all your devices.",
|
| 98 |
|
- |
if days == 1 { "day" } else { "days" },
|
| 99 |
|
- |
)));
|
| 100 |
|
- |
|
| 101 |
|
- |
let deleted = state.trash.deleted();
|
| 102 |
|
- |
if deleted.is_empty() {
|
| 103 |
|
- |
return body.with(Node::empty("Trash is empty."));
|
|
116 |
+ |
Bin {
|
|
117 |
+ |
days,
|
|
118 |
+ |
unit: if days == 1 { "day" } else { "days" },
|
|
119 |
+ |
gone: state
|
|
120 |
+ |
.trash
|
|
121 |
+ |
.deleted()
|
|
122 |
+ |
.iter()
|
|
123 |
+ |
.map(|entry| Gone {
|
|
124 |
+ |
hash: entry.hash.clone(),
|
|
125 |
+ |
filename: entry.filename(),
|
|
126 |
+ |
meta: format!(
|
|
127 |
+ |
"{} \u{b7} {}",
|
|
128 |
+ |
crate::ui::widgets::format_bytes(entry.size_bytes),
|
|
129 |
+ |
deleted_age(entry.age_secs),
|
|
130 |
+ |
),
|
|
131 |
+ |
short: (entry.hash.len() >= SHOWN).then(|| entry.hash[..SHOWN].to_owned()),
|
|
132 |
+ |
})
|
|
133 |
+ |
.collect(),
|
| 104 |
134 |
|
}
|
|
135 |
+ |
}
|
| 105 |
136 |
|
|
| 106 |
|
- |
let rows = deleted
|
| 107 |
|
- |
.iter()
|
| 108 |
|
- |
.map(|entry| {
|
| 109 |
|
- |
let mut row = Row::new(entry.filename()).meta(format!(
|
| 110 |
|
- |
"{} \u{b7} {}",
|
| 111 |
|
- |
crate::ui::widgets::format_bytes(entry.size_bytes),
|
| 112 |
|
- |
deleted_age(entry.age_secs),
|
| 113 |
|
- |
));
|
|
137 |
+ |
declare! {
|
|
138 |
+ |
/// The whole section, spliced into the settings body.
|
|
139 |
+ |
pub(super) shape section(trash: &Bin) -> Vec<Node>;
|
| 114 |
140 |
|
|
| 115 |
|
- |
if entry.hash.len() >= SHOWN {
|
| 116 |
|
- |
row = row.token(Tag::badge(&entry.hash[..SHOWN]));
|
|
141 |
+ |
section "Trash";
|
|
142 |
+ |
text "Deleted samples are kept here for {trash.days} {trash.unit}, then removed \
|
|
143 |
+ |
permanently. Restoring brings a sample back on all your devices.";
|
|
144 |
+ |
|
|
145 |
+ |
given trash.gone.is_empty() {
|
|
146 |
+ |
true -> empty "Trash is empty.";
|
|
147 |
+ |
otherwise -> list {
|
|
148 |
+ |
for entry in trash.gone.iter() {
|
|
149 |
+ |
row &entry.filename {
|
|
150 |
+ |
meta &entry.meta;
|
|
151 |
+ |
|
|
152 |
+ |
for short in entry.short.iter() {
|
|
153 |
+ |
token Tag::badge(short);
|
|
154 |
+ |
}
|
|
155 |
+ |
|
|
156 |
+ |
act "Restore" to post "/settings/trash/{entry.hash}/restore";
|
|
157 |
+ |
|
|
158 |
+ |
act "Delete permanently" to post "/settings/trash/{entry.hash}/purge" {
|
|
159 |
+ |
tone Danger;
|
|
160 |
+ |
confirm "Remove \"{entry.filename}\" now, skipping the \
|
|
161 |
+ |
{trash.days}-day window? This cannot be undone.";
|
|
162 |
+ |
}
|
|
163 |
+ |
}
|
| 117 |
164 |
|
}
|
| 118 |
|
- |
|
| 119 |
|
- |
row.act(Act::new(
|
| 120 |
|
- |
"Restore",
|
| 121 |
|
- |
Action::post(format!("/settings/trash/{}/restore", entry.hash)),
|
| 122 |
|
- |
))
|
| 123 |
|
- |
.act(
|
| 124 |
|
- |
Act::new(
|
| 125 |
|
- |
"Delete permanently",
|
| 126 |
|
- |
Action::post(format!("/settings/trash/{}/purge", entry.hash)),
|
| 127 |
|
- |
)
|
| 128 |
|
- |
.tone(Tone::Danger)
|
| 129 |
|
- |
.confirm(format!(
|
| 130 |
|
- |
"Remove \"{}\" now, skipping the {days}-day window? This cannot be undone.",
|
| 131 |
|
- |
entry.filename(),
|
| 132 |
|
- |
)),
|
| 133 |
|
- |
)
|
| 134 |
|
- |
})
|
| 135 |
|
- |
.collect();
|
| 136 |
|
- |
|
| 137 |
|
- |
body.with(Node::List { rows, more: None })
|
|
165 |
+ |
}
|
|
166 |
+ |
}
|
| 138 |
167 |
|
}
|
| 139 |
168 |
|
|
| 140 |
169 |
|
/// How long ago a sample was deleted.
|