diff --git a/src/app.rs b/src/app.rs index 02e2293..9254807 100644 --- a/src/app.rs +++ b/src/app.rs @@ -103,6 +103,7 @@ pub fn fill_state(state: &mut AppState) { state.dictionary.write().start().unwrap(); state.decks.write().start().unwrap(); state.history.write().start().unwrap(); + state.card_states.write().start().unwrap(); state.sync_data = Arc::new(RwLock::new(setting)) } diff --git a/src/core/deck.rs b/src/core/deck.rs index 11687fc..c7e687b 100644 --- a/src/core/deck.rs +++ b/src/core/deck.rs @@ -1,23 +1,23 @@ -use std::sync::Arc; -use std::time::Instant; -use chrono::Utc; use crate::app::AppState; use crate::core::srs::OrderModule::{Fsrs, RandomSRS, SemiRandomSRS, WorstWordsSRS}; use crate::core::srs::{ - OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule, + FsrsModule, OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule, WordOpenMode, WorstWordsSRSModule, }; use crate::data::entities::*; -use crate::data_provider::card_states::{CardStateRepositoryInner}; -use crate::notification::{MaybeError, NotificationData}; +use crate::data::repositories::Repository; +use crate::data_provider::card_states::CardStateRepositoryInner; +use crate::notification::MaybeError; +use chrono::Utc; use log::info; use rhai::{Engine, Scope}; -use crate::data::repositories::Repository; +use std::sync::Arc; +use std::time::Instant; #[derive(Clone)] pub struct DeckData { pub(crate) words: Vec>, - pub(crate) set: Vec>, + pub(crate) states: Vec>, current_word_index: Option, state: AppState, order_module: OrderModule, @@ -25,80 +25,58 @@ pub struct DeckData { settings: DeckSettings, } impl DeckData { - pub fn new( - settings: &DeckSettings, - state: AppState, - ) -> Result { - todo!() - // let state_for = state.clone(); - // - // let stats = state (settings, &state.connection.read())?; - // let words = stats - // .iter() - // .map(|stat| { - // state - // .dictionary - // .binary_search_by_key(&stat.word_id, |w| w.id) - // .expect("No word found in dictionary") - // }) - // .map(|index| &state.dictionary[index]) - // .cloned() - // .collect(); - // - // Ok(Self { - // set: stats, - // words, - // current_word_index: None, - // state: state_for, - // order_module: Fsrs(FsrsModule::new()), - // open_mode: settings.open_mode, - // settings: settings.clone(), - // }) + pub fn new(settings: &DeckSettings, open_mode: OrderMode, state: AppState) -> Self { + let states_repo = state.card_states.read(); + let mut query = states_repo.deck_states(settings.id); + let words = query + .iter(&states_repo) + .map(|card_state| EntityHandle::from_id(card_state.word_id)) + .collect(); + let states = query.handles().collect::>>(); + let fsrs = Fsrs(FsrsModule::new(Self::set_links(states.as_slice(), &states_repo).as_slice())); + drop(states_repo); + Self { + states, + words, + current_word_index: None, + state, + order_module: fsrs, + open_mode, + settings: settings.clone(), + } } pub fn next(&mut self) -> (EntityHandle, EntityHandle) { + if self.fsrs_finished() { + self.switch_order_engine() + } let repo = self.state.card_states.read(); - let links = Self::set_links(self.set.as_slice(), &repo); + let links = Self::set_links(self.states.as_slice(), &repo); let index = match self.order_module { - SemiRandomSRS(ref mut module) => { - if !module.initialized { - module.init(links.as_slice()); - } - module.next(links.as_slice()) - } + SemiRandomSRS(ref mut module) => module.next(links.as_slice()), - RandomSRS(ref mut module) => { - if !module.initialized { - module.init(links.as_slice()) - } - module.next(links.as_slice()) - } - WorstWordsSRS(ref mut module) => { - if !module.initialized { - module.init(links.as_slice()) - } - module.next(links.as_slice()) - } - Fsrs(ref mut module) => { - if !module.initialized { - module.init(links.as_slice()) - } - module.next(links.as_slice()) - } + RandomSRS(ref mut module) => module.next(links.as_slice()), + WorstWordsSRS(ref mut module) => module.next(links.as_slice()), + Fsrs(ref mut module) => module.next(links.as_slice()), }; self.current_word_index = Some(index); - (self.words[index].clone(), self.set[index].clone()) + (self.words[index].clone(), self.states[index].clone()) } - fn set_links<'a>(set: &[EntityHandle], repo: &'a CardStateRepositoryInner) -> Vec<&'a CardState> { - set.iter().map(|h| repo.get_ref_static(*h).unwrap()).collect::>() + fn set_links<'a>( + set: &[EntityHandle], + repo: &'a CardStateRepositoryInner, + ) -> Vec<&'a CardState> { + set.iter() + .map(|h| repo.get_ref_static(*h).unwrap()) + .collect::>() } pub fn open(&mut self, status: WordOpenMode) -> MaybeError { if let Some(index) = self.current_word_index { let mut repo = self.state.card_states.write(); - let card_state_handle = &mut self.set[index]; + let card_state_handle = &mut self.states[index]; let card_state = repo.get_mut(card_state_handle).unwrap(); let old_score = card_state.score; card_state.update(status); @@ -121,16 +99,6 @@ impl DeckData { } } - if let Fsrs(ref fsrs) = self.order_module - && fsrs.finished() - { - self.order_module = match self.open_mode { - OrderMode::Default => SemiRandomSRS(SemiRandomSRSModule::new()), - OrderMode::TrainWorstFirst => WorstWordsSRS(WorstWordsSRSModule::new()), - OrderMode::FullRandom => RandomSRS(RandomSRSModule::new()), - } - } - let word_id = card_state.word_id; repo.save(card_state_handle)?; @@ -145,23 +113,34 @@ impl DeckData { after: new_score, fsrs, })?; - todo!("Implement history pushing") - // let _ = push_note( - // self.settings.id, - // HistoryNote { - // timestamp: Utc::now(), - // word_id: word.word_id, - // answer: status, - // before: old_score, - // after: new_score, - // }, - // ); } Ok(()) } + + fn switch_order_engine(&mut self) { + let repo = self.state.card_states.read(); + let links = Self::set_links(self.states.as_slice(), &repo); + self.order_module = match self.open_mode { + OrderMode::Default => SemiRandomSRS(SemiRandomSRSModule::new(links.as_slice())), + OrderMode::TrainWorstFirst => WorstWordsSRS(WorstWordsSRSModule::new(links.as_slice())), + OrderMode::FullRandom => RandomSRS(RandomSRSModule::new(links.as_slice())), + } + } + + fn fsrs_finished(&self) -> bool { + if let Fsrs(ref fsrs) = self.order_module + && fsrs.finished() + { + info!("Fsrs finished. Infinite mode enabled"); + return true; + } + + false + } + pub fn len(&self) -> usize { - self.set.len() + self.states.len() } pub fn list(deck: &DeckSettings, state: &AppState) -> Vec> { @@ -179,17 +158,18 @@ impl DeckData { more.insert(iced.0.clone().into(), iced.1.clone().into()); } let mut scope = Scope::new(); - let group_name = groups[groups.binary_search_by_key(&word.group_id, |g| g.id).unwrap()].name.clone(); + let group_name = groups[groups + .binary_search_by_key(&word.group_id, |g| g.id) + .unwrap()] + .name + .clone(); scope .push_constant("id", word.id.0 as i64) .push_constant("key", word.key.clone()) .push_constant("value", word.value.clone()) .push_constant("tags", word.tags.clone()) .push_constant("more", more) - .push_constant( - "group", - group_name, - ); + .push_constant("group", group_name); let result = engine.eval_ast_with_scope::(&mut scope, &ast); if let Ok(result) = result { @@ -199,7 +179,7 @@ impl DeckData { })); info!("Collecting available words is {:?}", time.elapsed()); - let handles =query.handles().collect(); + let handles = query.handles().collect(); drop(query); handles } else { diff --git a/src/core/srs.rs b/src/core/srs.rs index 3e59c1f..a7a2936 100644 --- a/src/core/srs.rs +++ b/src/core/srs.rs @@ -1,6 +1,7 @@ use crate::data::entities::CardState; use chrono::{Local, TimeDelta}; use fsrs::FSRS; +use log::{debug, info}; use rand::distr::weighted::WeightedIndex; use rand::prelude::ThreadRng; use rand::prelude::{Distribution, SliceRandom}; @@ -31,24 +32,23 @@ pub enum OrderModule { Fsrs(FsrsModule), } pub trait SRSModule { + fn new(set: &[&CardState]) -> Self; fn next(&mut self, set: &[&CardState]) -> usize; fn open(&mut self, status: WordOpenMode, index: usize, updated_word: &mut CardState); - fn init(&mut self, set: &[&CardState]); } #[derive(Clone)] pub struct RandomSRSModule { basket: Vec, - pub(crate) initialized: bool, -} -impl RandomSRSModule { - pub(crate) fn new() -> RandomSRSModule { - Self { - basket: vec![], - initialized: false, - } - } } impl SRSModule for RandomSRSModule { + fn new(set: &[&CardState]) -> Self { + let mut basket = (0..set.len()).collect::>(); + basket.shuffle(&mut rand::rng()); + Self{ + basket, + } + } + fn next(&mut self, set: &[&CardState]) -> usize { match self.basket.pop() { Some(index) => index, @@ -61,29 +61,12 @@ impl SRSModule for RandomSRSModule { } fn open(&mut self, _: WordOpenMode, _: usize, _: &mut CardState) {} - - fn init(&mut self, set: &[&CardState]) { - self.initialized = true; - self.basket = (0..set.len()).collect::>(); - self.basket.shuffle(&mut rand::rng()) - } } #[derive(Clone)] pub struct SemiRandomSRSModule { history: Vec, last_weights: WeightedIndex, generator: ThreadRng, - pub(crate) initialized: bool, -} -impl SemiRandomSRSModule { - pub(crate) fn new() -> SemiRandomSRSModule { - SemiRandomSRSModule { - history: vec![], - last_weights: WeightedIndex::new([1.0]).unwrap(), - generator: rng(), - initialized: false, - } - } } impl SRSModule for SemiRandomSRSModule { fn next(&mut self, set: &[&CardState]) -> usize { @@ -106,13 +89,18 @@ impl SRSModule for SemiRandomSRSModule { .update_weights(&[(index, &new_weight)]) .expect("update weights error"); } - fn init(&mut self, set: &[&CardState]) { - self.initialized = true; + fn new(set: &[&CardState]) -> Self { + let weights = set .iter() .map(|s| (100.0 / s.calculated_score()).powf(2.0) * 2.0) .collect::>(); - self.last_weights = WeightedIndex::new(weights).expect("Incorrect weights update"); + + Self{ + generator: rng(), + last_weights: WeightedIndex::new(weights).expect("Incorrect weights update"), + history: vec![] + } } } impl SemiRandomSRSModule { @@ -122,7 +110,6 @@ impl SemiRandomSRSModule { } #[derive(Clone)] pub struct WorstWordsSRSModule { - pub(crate) initialized: bool, pool_size: usize, rounds_remaining: u8, pool: Vec, @@ -148,14 +135,8 @@ impl SRSModule for WorstWordsSRSModule { fn open(&mut self, _: WordOpenMode, _: usize, _: &mut CardState) {} - fn init(&mut self, _: &[&CardState]) { - self.initialized = true; - } -} -impl WorstWordsSRSModule { - pub(crate) fn new() -> WorstWordsSRSModule { + fn new(_: &[&CardState]) -> Self { WorstWordsSRSModule { - initialized: false, pool_size: PARTIAL_POOL_SIZE, rounds_count: 2, rounds_remaining: 0, @@ -163,6 +144,8 @@ impl WorstWordsSRSModule { queue: vec![], } } +} +impl WorstWordsSRSModule { fn fill_pool(&mut self, set: &[&CardState]) { let mut sorted = set.iter().enumerate().collect::>(); sorted.sort_by_key(|c| c.1.score); @@ -183,10 +166,31 @@ pub struct FsrsModule { queue: Vec, rng: ThreadRng, fsrs: FSRS, - pub(crate) initialized: bool, } impl SRSModule for FsrsModule { + fn new(set: &[&CardState]) -> Self { + let today = Local::now().date_naive(); + + let mut new = Self { + today_set: set + .iter() + .enumerate() + .filter(|(_, c)| c.fsrs_open_day <= today) + .map(|(index, _)| index) + .collect(), + current_group: vec![], + queue: vec![], + solved: vec![], + rng: rng(), + fsrs: FSRS::default(), + }; + new.today_set.shuffle(&mut new.rng); + new.fill_partial_pool(); + + new + } + fn next(&mut self, _: &[&CardState]) -> usize { if self.current_group.is_empty() { self.fill_partial_pool(); @@ -222,45 +226,25 @@ impl SRSModule for FsrsModule { let offset = ((rng().random::() - 0.5) * 3.0).round() as i32; next_day += TimeDelta::days(offset as i64); } + + let first_time = updated_word.fsrs_memory.is_none(); + updated_word.fsrs_memory = Some(next.memory); updated_word.fsrs_open_day = next_day; updated_word.fsrs_opened = updated_word.last_open.date_naive(); - if updated_word.fsrs_open_day != today { + if (updated_word.fsrs_open_day != today && !first_time) || (first_time && status == WordOpenMode::Easy) { + info!("For today word on index {} solved", index); self.solved.push(index); let index = self.current_group.iter().position(|x| *x == index).unwrap(); self.current_group.remove(index); } } - - fn init(&mut self, set: &[&CardState]) { - self.rng = rng(); - let today = Local::now().date_naive(); - self.today_set = set - .iter() - .enumerate() - .filter(|(_, c)| c.fsrs_open_day <= today) - .map(|(index, _)| index) - .collect(); - self.today_set.shuffle(&mut self.rng); - self.fill_partial_pool(); - self.initialized = true; - } } impl FsrsModule { - pub fn new() -> Self { - Self { - today_set: vec![], - current_group: vec![], - queue: vec![], - solved: vec![], - rng: Default::default(), - fsrs: FSRS::default(), - initialized: false, - } - } fn fill_partial_pool(&mut self) { + debug!("Filling partial pool"); self.current_group.extend( self.today_set .iter() @@ -281,7 +265,7 @@ impl FsrsModule { } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum WordOpenMode { Easy, Ok, diff --git a/src/data_provider/card_states.rs b/src/data_provider/card_states.rs index 975385b..266e0ab 100644 --- a/src/data_provider/card_states.rs +++ b/src/data_provider/card_states.rs @@ -1,5 +1,6 @@ +use std::sync::Arc; use crate::data::entities::{CardState, DeckSettings}; -use crate::data::repositories::{DataRepository, DataRepositoryInner}; +use crate::data::repositories::{DataRepository, DataRepositoryInner, Query}; use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet}; use crate::notification::NotificationData::{DbLoadError, DbSaveError}; use crate::notification::{MaybeError, NotificationData}; @@ -8,11 +9,13 @@ use fsrs::MemoryState; use log::info; use rusqlite::{Connection, Error, Params, Row, params}; use std::time::Instant; +use crate::data::Id; pub type CardStateRepository = - DataRepository>; + DataRepository; pub type CardStateRepositoryInner = - DataRepositoryInner>; + DataRepositoryInner; +pub type CardStateDbLayer = SqliteDbLayer; #[derive(Clone)] pub struct CardStatesQuerySet; impl TableAccessQuerySet for CardStatesQuerySet { @@ -63,10 +66,10 @@ impl TableAccessQuerySet for CardStatesQuerySet { ( entity.score, entity.last_open.timestamp(), - entity.id, - entity.fsrs_opened, + entity.fsrs_opened.to_epoch_days(), memory_to_i64(&entity.fsrs_memory), - entity.fsrs_open_day, + entity.fsrs_open_day.to_epoch_days(), + entity.id, ) } } @@ -118,74 +121,8 @@ fn memory_to_i64(value: &Option) -> i64 { value.stability.to_bits() as i64 | ((value.difficulty.to_bits() as i64) << 32) } -pub fn add_stat_list(stats: &mut [CardState], connection: &mut Connection) -> MaybeError { - let time = Instant::now(); - let tx = connection - .transaction() - .expect("Failed to create transaction"); - let count = stats.len(); - - { - let mut stmt = tx - .prepare( - "INSERT INTO states (word_id, set_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day) VALUES (?1, ?2, ?3, ?4)", - ) - .expect("Failed to prepare state query"); - - for stat in stats.iter() { - stmt.execute(params![ - stat.word_id, - stat.deck_id, - stat.score, - stat.last_open.timestamp(), - stat.fsrs_opened.to_epoch_days(), - memory_to_i64(&stat.fsrs_memory), - stat.fsrs_open_day.to_epoch_days() - ]) - .map_err(|_| DbSaveError)?; - } +impl CardStateRepositoryInner { + pub fn deck_states(&self, deck_id: Id) -> Query { + self.query(Arc::new(move |state| {state.deck_id == deck_id})) } - - let last_index: u32 = tx.last_insert_rowid() as u32; - tx.commit().expect("Failed to commit"); - - let start_index = last_index - (count as u32) + 1; - - for (index, id) in (start_index..=last_index).enumerate() { - stats[index].id = id.into(); - } - - info!("Added {} cards for {:?}", stats.len(), time.elapsed()); - Ok(()) -} - -pub fn update_stat_score(stat: &CardState, connection: &Connection) -> MaybeError { - let time = Instant::now(); - - connection - .execute( - "UPDATE states SET score = ?1, last_opened = ?2 WHERE id = ?3", - (&stat.score, &stat.last_open.timestamp(), &stat.id), - ) - .map_err(|_| DbSaveError)?; - info!("Updated stat: {}", time.elapsed().as_millis()); - Ok(()) -} - -pub fn delete_stats(stat: &[CardState], connection: &Connection) -> MaybeError { - if stat.is_empty() { - return Ok(()); - } - let ids = stat - .iter() - .map(|s| s.id.into()) - .collect::>() - .join(", "); - connection - .execute( - format!("DELETE FROM states WHERE id in ({ids})").as_str(), - (), - ) - .map_err(|_| DbSaveError)?; - Ok(()) -} +} \ No newline at end of file diff --git a/src/data_provider/history.rs b/src/data_provider/history.rs index 889c768..17007b1 100644 --- a/src/data_provider/history.rs +++ b/src/data_provider/history.rs @@ -21,7 +21,7 @@ impl TableAccessQuerySet for HistoryQuerySet { } fn add() -> &'static str { - "INSERT INTO history (deck_id, state_id, timestamp, answer, before, after, fsrs) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)" + "INSERT INTO history (deck_id, word_id, timestamp, answer, before, after, fsrs) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)" } fn name() -> &'static str { diff --git a/src/pages/repetition.rs b/src/pages/repetition.rs index dec7ee6..9e277b9 100644 --- a/src/pages/repetition.rs +++ b/src/pages/repetition.rs @@ -2,11 +2,13 @@ use crate::app::AppState; use crate::core::deck::DeckData; use crate::core::srs::WordOpenMode; use crate::data::Id; -use crate::data::entities::{CardState, DeckSettings, WordData}; +use crate::data::entities::{CardState, WordData}; +use crate::data::repositories::Repository; use crate::data_provider::voice::get_voice; use crate::navigation::Page::PreviousPage; use crate::navigation::{KeyPressedPage, NavigatedPage, Page, RootMessage}; use crate::notification::{MaybeError, NotificationData}; +use crate::pages::repetitions::DeckRunData; use crate::styling::*; use crate::{push_error, push_error_task}; use iced::alignment::Horizontal::Center; @@ -18,11 +20,10 @@ use rodio::MixerDeviceSink; use std::collections::HashSet; use std::sync::Arc; use tokio::task::spawn_blocking; -use crate::data::repositories::Repository; pub struct RepetitionState { state: AppState, - pub settings: DeckSettings, + pub settings: DeckRunData, pub set: DeckData, current_word: WordData, current_statistic: CardState, @@ -102,10 +103,10 @@ impl NavigatedPage for RepetitionState { impl RepetitionState { pub(crate) fn new( - set: DeckSettings, + set: DeckRunData, state: AppState, ) -> Result { - let mut card_set = DeckData::new(&set, state.clone())?; + let mut card_set = DeckData::new(&set, set.open_mode, state.clone()); let (word, stat) = card_set.next(); let word = state.dictionary.read().get_static(word).unwrap(); let stat = state.card_states.read().get_static(stat).unwrap(); diff --git a/src/pages/repetitions.rs b/src/pages/repetitions.rs index b4ac8a4..a78d714 100644 --- a/src/pages/repetitions.rs +++ b/src/pages/repetitions.rs @@ -33,7 +33,7 @@ use std::sync::Arc; #[derive(Clone)] pub struct RepetitionsState { selected_deck_handle: Option>, - decks: BTreeMap, DeckViewData>, + decks: BTreeMap, DeckRunData>, state: AppState, } @@ -63,7 +63,7 @@ impl NavigatedPage for RepetitionsState { } if let Ok(repetition_state) = - RepetitionState::new(selected_set.general_settings.clone(), self.state.clone()) + RepetitionState::new(selected_set.clone(), self.state.clone()) { return Some(Repetition(repetition_state)); } @@ -111,7 +111,7 @@ impl NavigatedPage for RepetitionsState { let handle = self.state.decks.write().create(general.clone()); self.decks.insert( handle, - DeckViewData { + DeckRunData { settings_handle: handle, general_settings: general, existing_words_indices: Default::default(), @@ -258,7 +258,7 @@ impl RepetitionsState { fn get_decks_from_state( state: &AppState, - ) -> BTreeMap, DeckViewData> { + ) -> BTreeMap, DeckRunData> { state .decks .read() @@ -267,7 +267,7 @@ impl RepetitionsState { .map(|(index, deck)| { ( EntityHandle::new(deck.id, index, true), - DeckViewData { + DeckRunData { settings_handle: EntityHandle::new(deck.id, index, true), general_settings: deck.clone(), existing_words_indices: Default::default(), @@ -284,7 +284,7 @@ impl RepetitionsState { } impl RepetitionsState { - fn launch_delete_button(&self, deck: &DeckViewData) -> Element<'_, RepetitionsMessage> { + fn launch_delete_button(&self, deck: &DeckRunData) -> Element<'_, RepetitionsMessage> { if deck.id.is_valid() { if deck.append_mode == AppendMode::Manual && deck.existing_words_indices.is_empty() { return text!("Добавьте карточки или измените фильтр").into(); @@ -468,7 +468,7 @@ impl RepetitionsState { }) .into() } - fn word_append_panel(&self, deck: &DeckViewData) -> Element<'_, RepetitionsMessage> { + fn word_append_panel(&self, deck: &DeckRunData) -> Element<'_, RepetitionsMessage> { column![ text!("Режим добавления карточек"), row![ @@ -491,7 +491,7 @@ impl RepetitionsState { .spacing(HALF_SPACING) .into() } - fn adder_panel(&self, deck: &DeckViewData) -> Element<'_, RepetitionsMessage> { + fn adder_panel(&self, deck: &DeckRunData) -> Element<'_, RepetitionsMessage> { match deck.append_mode { AppendMode::Full => space().into(), AppendMode::Manual => row![ @@ -511,7 +511,7 @@ impl RepetitionsState { let available = selected_deck.available_words_indices.len(); text!("{} слова добавлено из {}", now, available).into() } - fn count_view(&self, deck: &DeckViewData) -> Element<'_, RepetitionsMessage> { + fn count_view(&self, deck: &DeckRunData) -> Element<'_, RepetitionsMessage> { if let Some(count) = deck.count { return text!("Количество слов: {}", count).into(); } @@ -547,7 +547,7 @@ impl RepetitionsState { } impl RepetitionsState { - fn append_all_words(state: &mut AppState, deck: &DeckViewData) -> MaybeError { + fn append_all_words(state: &mut AppState, deck: &DeckRunData) -> MaybeError { let existing = &deck.existing_words_indices; let mut created_set = HashSet::with_capacity(existing.len()); existing.iter().for_each(|c| { @@ -593,10 +593,10 @@ impl RepetitionsState { } } - fn selected_deck(&self) -> Option<&DeckViewData> { + fn selected_deck(&self) -> Option<&DeckRunData> { self.decks.get(&self.selected_deck_handle?) } - fn selected_deck_mut(&mut self) -> Option<&mut DeckViewData> { + fn selected_deck_mut(&mut self) -> Option<&mut DeckRunData> { self.decks.get_mut(&self.selected_deck_handle?) } @@ -641,18 +641,18 @@ pub enum RepetitionsMessage { } #[derive(Clone)] -struct DeckViewData { - settings_handle: EntityHandle, - general_settings: DeckSettings, +pub struct DeckRunData { + pub settings_handle: EntityHandle, + pub general_settings: DeckSettings, existing_words_indices: Vec>, available_words_indices: Vec>, indices_ready: bool, - append_mode: AppendMode, + pub append_mode: AppendMode, count: Option, - open_mode: OrderMode, + pub open_mode: OrderMode, } -impl Deref for DeckViewData { +impl Deref for DeckRunData { type Target = DeckSettings; fn deref(&self) -> &Self::Target {