|
12 | 12 | // See the License for the specific language governing permissions and |
13 | 13 | // limitations under the License. |
14 | 14 |
|
15 | | -use axum::Form; |
16 | 15 | use axum::extract::State; |
17 | 16 | use axum::http::StatusCode; |
18 | 17 | use axum::response::Html; |
19 | | -use axum::response::Redirect; |
20 | 18 | use maud::Markup; |
21 | 19 | use maud::PreEscaped; |
22 | 20 | use maud::html; |
23 | | -use serde::Deserialize; |
24 | 21 |
|
25 | 22 | use crate::drill::state::ServerState; |
26 | 23 | use crate::drill::template::page_template; |
27 | | -use crate::error::Fallible; |
28 | | -use crate::fsrs::Grade; |
29 | 24 | use crate::markdown::markdown_to_html; |
30 | 25 | use crate::types::card::CardContent; |
31 | | -use crate::types::review::Review; |
32 | | -use crate::types::review::update_card; |
33 | | -use crate::types::timestamp::Timestamp; |
34 | 26 |
|
35 | 27 | const CLOZE_TAG: &str = "CLOZE_DELETION"; |
36 | 28 |
|
@@ -166,140 +158,3 @@ pub async fn get_handler(State(state): State<ServerState>) -> (StatusCode, Html< |
166 | 158 | let html = page_template(body); |
167 | 159 | (StatusCode::OK, Html(html.into_string())) |
168 | 160 | } |
169 | | - |
170 | | -#[derive(Debug, Deserialize)] |
171 | | -enum Action { |
172 | | - Reveal, |
173 | | - Undo, |
174 | | - End, |
175 | | - Forgot, |
176 | | - Hard, |
177 | | - Good, |
178 | | - Easy, |
179 | | -} |
180 | | - |
181 | | -#[derive(Deserialize)] |
182 | | -pub struct FormData { |
183 | | - action: Action, |
184 | | -} |
185 | | - |
186 | | -pub async fn post_handler( |
187 | | - State(state): State<ServerState>, |
188 | | - Form(form): Form<FormData>, |
189 | | -) -> Redirect { |
190 | | - match action_handler(state, form.action).await { |
191 | | - Ok(_) => {} |
192 | | - Err(e) => { |
193 | | - log::error!("error: {e}"); |
194 | | - } |
195 | | - } |
196 | | - Redirect::to("/") |
197 | | -} |
198 | | - |
199 | | -async fn action_handler(state: ServerState, action: Action) -> Fallible<()> { |
200 | | - let mut mutable = state.mutable.lock().unwrap(); |
201 | | - let today = state.session_started_at.local_date(); |
202 | | - match action { |
203 | | - Action::Reveal => { |
204 | | - if mutable.reveal { |
205 | | - log::error!("Revealing a card that is already revealed."); |
206 | | - } else { |
207 | | - mutable.reveal = true; |
208 | | - } |
209 | | - } |
210 | | - Action::Undo => { |
211 | | - if !mutable.reviewed.is_empty() { |
212 | | - let r = mutable.reviews.pop().unwrap(); |
213 | | - let card = if r.grade == Grade::Forgot || r.grade == Grade::Hard { |
214 | | - let _ = mutable.reviewed.pop().unwrap(); |
215 | | - mutable.cards.pop().unwrap() |
216 | | - } else { |
217 | | - mutable.reviewed.pop().unwrap() |
218 | | - }; |
219 | | - mutable.cards.insert(0, card); |
220 | | - } else { |
221 | | - log::error!("No reviewed cards to undo."); |
222 | | - } |
223 | | - } |
224 | | - Action::End => { |
225 | | - log::debug!("Session completed"); |
226 | | - let session_ended_at = Timestamp::now(); |
227 | | - let reviews = mutable.reviews.clone(); |
228 | | - mutable |
229 | | - .db |
230 | | - .save_session(state.session_started_at, session_ended_at, reviews)?; |
231 | | - mutable.finished = true; |
232 | | - } |
233 | | - Action::Forgot | Action::Hard | Action::Good | Action::Easy => { |
234 | | - if !mutable.reveal { |
235 | | - log::error!("Answering a card that is not revealed."); |
236 | | - } else { |
237 | | - let card = mutable.cards.remove(0); |
238 | | - let hash = card.hash(); |
239 | | - let latest_review = match mutable.db.get_latest_review(hash)? { |
240 | | - Some(r) => Some(r), |
241 | | - None => { |
242 | | - // Look through the in-memory review database. |
243 | | - let mut found: Option<Review> = None; |
244 | | - for r in mutable.reviews.iter().rev() { |
245 | | - if r.card_hash == hash { |
246 | | - found = Some(r.clone()); |
247 | | - break; |
248 | | - } |
249 | | - } |
250 | | - found |
251 | | - } |
252 | | - }; |
253 | | - let grade: Grade = match action { |
254 | | - Action::Forgot => Grade::Forgot, |
255 | | - Action::Hard => Grade::Hard, |
256 | | - Action::Good => Grade::Good, |
257 | | - Action::Easy => Grade::Easy, |
258 | | - _ => unreachable!(), |
259 | | - }; |
260 | | - let parameters = update_card(latest_review, grade, today); |
261 | | - let review = Review { |
262 | | - card_hash: hash, |
263 | | - reviewed_at: Timestamp::now(), |
264 | | - grade, |
265 | | - stability: parameters.stability, |
266 | | - difficulty: parameters.difficulty, |
267 | | - due_date: parameters.due_date, |
268 | | - }; |
269 | | - mutable.reviews.push(review); |
270 | | - |
271 | | - let diff_percent = ((parameters.difficulty - 1.0) / (9.0)) * 100.0; |
272 | | - log::debug!( |
273 | | - "{} {} S={:.2}d D={:.2}% due={}", |
274 | | - &hash.to_hex()[..8], |
275 | | - grade.as_str(), |
276 | | - parameters.stability, |
277 | | - diff_percent, |
278 | | - parameters.due_date.into_inner() |
279 | | - ); |
280 | | - |
281 | | - // Cards graded `Forgot` or `Hard` are put at the back of the |
282 | | - // queue. |
283 | | - if grade == Grade::Forgot || grade == Grade::Hard { |
284 | | - mutable.cards.push(card.clone()); |
285 | | - } |
286 | | - |
287 | | - mutable.reviewed.push(card); |
288 | | - |
289 | | - mutable.reveal = false; |
290 | | - |
291 | | - // Was this the last card? |
292 | | - if mutable.cards.is_empty() { |
293 | | - log::debug!("Session completed"); |
294 | | - let session_ended_at = Timestamp::now(); |
295 | | - let reviews = mutable.reviews.clone(); |
296 | | - mutable |
297 | | - .db |
298 | | - .save_session(state.session_started_at, session_ended_at, reviews)?; |
299 | | - mutable.finished = true; |
300 | | - } |
301 | | - } |
302 | | - } |
303 | | - } |
304 | | - Ok(()) |
305 | | -} |
0 commit comments