Migrate to Id type

This commit is contained in:
2026-08-18 12:47:06 +03:00
parent 99b3818181
commit 97c76350e1
12 changed files with 106 additions and 62 deletions
+4 -4
View File
@@ -1,4 +1,4 @@
use crate::lang::{DeckSettings, OrderMode}; use crate::lang::{DeckSettings, OrderMode, INVALID_ID};
use rusqlite::Connection; use rusqlite::Connection;
@@ -43,11 +43,11 @@ pub fn add_set(set: &mut DeckSettings, connection: &Connection) {
) )
.unwrap_or_else(|e| {println!("{}", e); 0}); .unwrap_or_else(|e| {println!("{}", e); 0});
set.id = index; set.id = index.into();
} }
pub fn update_card_set(set: &mut DeckSettings, connection: &Connection) { pub fn update_card_set(set: &mut DeckSettings, connection: &Connection) {
if set.id == 0 { if set.id == INVALID_ID {
add_set(set, connection); add_set(set, connection);
} else { } else {
connection connection
@@ -66,7 +66,7 @@ pub fn update_card_set(set: &mut DeckSettings, connection: &Connection) {
} }
pub fn delete_set(set: &DeckSettings, connection: &Connection) { pub fn delete_set(set: &DeckSettings, connection: &Connection) {
if set.id == 0 { if set.id == INVALID_ID {
return; return;
} }
connection connection
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::lang::{DeckSettings, CardStatistics}; use crate::lang::{DeckSettings, CardStatistics, INVALID_ID};
use rusqlite::Connection; use rusqlite::Connection;
use std::time::Instant; use std::time::Instant;
@@ -62,7 +62,7 @@ pub fn add_stat_list(stat: &mut [CardStatistics], connection: &Connection) {
let start_index = last_index - (stat.len() as u32) + 1; let start_index = last_index - (stat.len() as u32) + 1;
for (index, id) in (start_index..=last_index).enumerate() { for (index, id) in (start_index..=last_index).enumerate() {
stat[index].id = id; stat[index].id = id.into();
} }
println!("Added {} cards for {:?}", stat.len(), time.elapsed()); println!("Added {} cards for {:?}", stat.len(), time.elapsed());
@@ -103,7 +103,7 @@ pub fn update_stat_score(stat: &CardStatistics, connection: &Connection) {
} }
pub fn delete_stat(stat: &CardStatistics, connection: &Connection) { pub fn delete_stat(stat: &CardStatistics, connection: &Connection) {
if stat.id == 0 { if stat.id == INVALID_ID {
return; return;
} }
connection connection
+5 -5
View File
@@ -1,5 +1,5 @@
use crate::dictionary::app_data_dir; use crate::dictionary::app_data_dir;
use crate::lang::WordOpenMode; use crate::lang::{Id, WordOpenMode};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use std::fs; use std::fs;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
@@ -7,7 +7,7 @@ use std::io::Write;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::path::PathBuf; use std::path::PathBuf;
pub fn get_history_of_set(id: u32) -> Vec<HistoryItem> { pub fn get_history_of_set(id: Id) -> Vec<HistoryItem> {
let app_dir = history_dir(); let app_dir = history_dir();
let head = app_dir.clone().join(format!("set_{}_history.csv", id)); let head = app_dir.clone().join(format!("set_{}_history.csv", id));
let mut lines = Vec::new(); let mut lines = Vec::new();
@@ -32,7 +32,7 @@ fn parse_history_items(strings: Vec<String>) -> Vec<HistoryItem> {
if let [time, word, mode, before, after] = string.split(';').collect::<Vec<&str>>()[..] { if let [time, word, mode, before, after] = string.split(';').collect::<Vec<&str>>()[..] {
items.push(HistoryItem { items.push(HistoryItem {
timestamp: DateTime::from_timestamp(time.parse().unwrap(), 0).unwrap(), timestamp: DateTime::from_timestamp(time.parse().unwrap(), 0).unwrap(),
word_id: word.parse::<u32>().unwrap(), word_id: word.parse::<u32>().unwrap().into(),
mode: match mode.parse::<u8>().unwrap() { mode: match mode.parse::<u8>().unwrap() {
2 => WordOpenMode::Hard, 2 => WordOpenMode::Hard,
3 => WordOpenMode::Ok, 3 => WordOpenMode::Ok,
@@ -48,7 +48,7 @@ fn parse_history_items(strings: Vec<String>) -> Vec<HistoryItem> {
items items
} }
pub fn push_note(set_id: u32, item: HistoryItem) { pub fn push_note(set_id: Id, item: HistoryItem) {
let app_dir = history_dir(); let app_dir = history_dir();
let path = app_dir.clone().join(format!("set_{}_history.csv", set_id)); let path = app_dir.clone().join(format!("set_{}_history.csv", set_id));
@@ -84,7 +84,7 @@ pub fn history_dir() -> PathBuf {
#[derive(Clone)] #[derive(Clone)]
pub struct HistoryItem { pub struct HistoryItem {
pub timestamp: DateTime<Utc>, pub timestamp: DateTime<Utc>,
pub word_id: u32, pub word_id: Id,
pub mode: WordOpenMode, pub mode: WordOpenMode,
pub before: u8, pub before: u8,
pub after: u8, pub after: u8,
+8 -8
View File
@@ -1,4 +1,4 @@
use crate::lang::{WordData, WordGroup}; use crate::lang::{WordData, WordGroup, INVALID_ID};
use rusqlite::{Connection, params}; use rusqlite::{Connection, params};
use std::collections::HashMap; use std::collections::HashMap;
@@ -20,7 +20,7 @@ pub fn add_word(word: &mut WordData, connection: &Connection) {
0 0
}); });
word.id = index; word.id = index.into();
} }
pub fn add_words(words: &mut [WordData], connection: &mut Connection) { pub fn add_words(words: &mut [WordData], connection: &mut Connection) {
@@ -59,12 +59,12 @@ pub fn add_words(words: &mut [WordData], connection: &mut Connection) {
let start_index = last_index - (count as u32) + 1; let start_index = last_index - (count as u32) + 1;
for (index, id) in (start_index..=last_index).enumerate() { for (index, id) in (start_index..=last_index).enumerate() {
words[index].id = id; words[index].id = id.into();
} }
} }
pub fn update_word(word: &mut WordData, connection: &Connection) { pub fn update_word(word: &mut WordData, connection: &Connection) {
if word.id == 0 { if word.id == INVALID_ID {
add_word(word, connection); add_word(word, connection);
} else { } else {
connection connection
@@ -86,7 +86,7 @@ pub fn update_word(word: &mut WordData, connection: &Connection) {
} }
pub fn delete_word(word: &WordData, connection: &Connection) { pub fn delete_word(word: &WordData, connection: &Connection) {
if word.id == 0 { if word.id == INVALID_ID {
return; return;
} }
connection connection
@@ -156,11 +156,11 @@ pub fn add_group(group: &mut WordGroup, connection: &Connection) {
0 0
}); });
group.id = index; group.id = index.into();
} }
pub fn update_group(group: &mut WordGroup, connection: &Connection) { pub fn update_group(group: &mut WordGroup, connection: &Connection) {
if group.id == 0 { if group.id == INVALID_ID {
add_group(group, connection); add_group(group, connection);
} else { } else {
connection connection
@@ -176,7 +176,7 @@ pub fn update_group(group: &mut WordGroup, connection: &Connection) {
} }
pub fn delete_group(group: &WordGroup, connection: &Connection) { pub fn delete_group(group: &WordGroup, connection: &Connection) {
if group.id == 0 { if group.id == INVALID_ID {
return; return;
} }
connection connection
+8 -8
View File
@@ -2,7 +2,7 @@ use crate::data_provider::words::{delete_group, delete_word, update_group, updat
use crate::dictionary::DictionaryMessage::*; use crate::dictionary::DictionaryMessage::*;
use crate::dictionary_test::DictionaryQuizState; use crate::dictionary_test::DictionaryQuizState;
use crate::import::ImportState; use crate::import::ImportState;
use crate::lang::{WordData, WordGroup}; use crate::lang::{WordData, WordGroup, INVALID_ID};
use crate::navigation::Page::{Import, Word}; use crate::navigation::Page::{Import, Word};
use crate::navigation::{NavigatedPage, Page}; use crate::navigation::{NavigatedPage, Page};
use crate::styling::*; use crate::styling::*;
@@ -96,7 +96,7 @@ impl NavigatedPage<DictionaryMessage> for DictionaryState {
let dict = &state.dictionary; let dict = &state.dictionary;
word = dict[*index].clone(); word = dict[*index].clone();
} }
if word.id != 0 { if word.id != INVALID_ID {
return Some(Word(WordState::new(word, *index, self.state.clone()))); return Some(Word(WordState::new(word, *index, self.state.clone())));
} }
} }
@@ -172,7 +172,7 @@ impl NavigatedPage<DictionaryMessage> for DictionaryState {
} }
Include(i, b) => self.include_map[i] = b, Include(i, b) => self.include_map[i] = b,
IncludeTag(t, v) => { IncludeTag(t, v) => {
let index: u32; let index;
{ {
let state = self.state.lock().unwrap(); let state = self.state.lock().unwrap();
index = state.word_groups[self.selected_group_index].id; index = state.word_groups[self.selected_group_index].id;
@@ -197,7 +197,7 @@ impl NavigatedPage<DictionaryMessage> for DictionaryState {
let state = &mut self.state.lock().unwrap(); let state = &mut self.state.lock().unwrap();
state.word_groups.push(WordGroup { state.word_groups.push(WordGroup {
id: 0, id: 0.into(),
name: format!("Группа слов {}", random_range(100..1000)), name: format!("Группа слов {}", random_range(100..1000)),
}); });
} }
@@ -222,7 +222,7 @@ impl NavigatedPage<DictionaryMessage> for DictionaryState {
} }
SelectGroup(i) => { SelectGroup(i) => {
self.selected_group_index = i; self.selected_group_index = i;
let index: u32; let index;
{ {
let state = self.state.lock().unwrap(); let state = self.state.lock().unwrap();
index = state.word_groups[i].id; index = state.word_groups[i].id;
@@ -423,7 +423,7 @@ impl DictionaryState {
let line_button = || { let line_button = || {
let action = WordAction(index); let action = WordAction(index);
if data.id == 0 { if data.id == INVALID_ID {
return button("-").on_press(action).style(|_x, _status| Style { return button("-").on_press(action).style(|_x, _status| Style {
background: None, background: None,
text_color: Color::BLACK, text_color: Color::BLACK,
@@ -532,7 +532,7 @@ impl DictionaryState {
}); });
} }
fn update_words_include(&mut self, group_id: u32) { fn update_words_include(&mut self, group_id: crate::lang::Id) {
let include_tags = self let include_tags = self
.tag_map .tag_map
.iter() .iter()
@@ -626,6 +626,6 @@ struct WordLineState {
key: String, key: String,
value: String, value: String,
tags: String, tags: String,
id: u32, id: crate::lang::Id,
index: usize, index: usize,
} }
+1 -1
View File
@@ -46,7 +46,7 @@ impl NavigatedPage<HistoryMessage> for HistoryState {
} }
impl HistoryState { impl HistoryState {
pub fn new(id: u32, state: Arc<Mutex<AppState>>) -> Self { pub fn new(id: crate::lang::Id, state: Arc<Mutex<AppState>>) -> Self {
let state = state.lock().unwrap(); let state = state.lock().unwrap();
let history = get_history_of_set(id); let history = get_history_of_set(id);
let words = history let words = history
+1 -1
View File
@@ -446,7 +446,7 @@ impl ImportState {
let map_indices = Self::get_mapping_indices(&group); let map_indices = Self::get_mapping_indices(&group);
let mut group_entity = WordGroup { let mut group_entity = WordGroup {
id: 0, id: 0.into(),
name: group.name.clone(), name: group.name.clone(),
}; };
{ {
+60 -16
View File
@@ -11,8 +11,11 @@ use rayon::iter::IndexedParallelIterator;
use rayon::iter::IntoParallelRefIterator; use rayon::iter::IntoParallelRefIterator;
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use rhai::{Engine, Scope}; use rhai::{Engine, Scope};
use serde::{Deserialize, Serialize}; use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
use rusqlite::ToSql;
use std::cmp::PartialEq;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Instant; use std::time::Instant;
@@ -20,6 +23,9 @@ const MAX_HISTORY_LEN: usize = 20;
const MAX_HISTORY_LEN_PART: f32 = 0.33; const MAX_HISTORY_LEN_PART: f32 = 0.33;
const MAX_SCORE: u8 = 25; const MAX_SCORE: u8 = 25;
const FADE_PER_DAY: f32 = 0.95; const FADE_PER_DAY: f32 = 0.95;
#[derive(Clone, PartialEq, Eq, Debug, Hash, Copy)]
pub(crate) struct Id(u32);
pub const INVALID_ID: Id = Id(0);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct KanaSet { pub struct KanaSet {
name: String, name: String,
@@ -225,40 +231,40 @@ impl PartialEq<Self> for KanaSet {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug)]
pub struct WordData { pub struct WordData {
pub id: u32, pub id: Id,
pub key: String, pub key: String,
pub value: String, pub value: String,
pub tags: String, pub tags: String,
pub additional: HashMap<String, String>, pub additional: HashMap<String, String>,
pub group_id: u32, pub group_id: Id,
} }
impl WordData { impl WordData {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
id: 0, id: 0.into(),
key: String::new(), key: String::new(),
value: String::new(), value: String::new(),
tags: String::new(), tags: String::new(),
additional: Default::default(), additional: Default::default(),
group_id: 1, group_id: 1.into(),
} }
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct WordGroup { pub struct WordGroup {
pub id: u32, pub id: Id,
pub name: String, pub name: String,
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct CardStatistics { pub struct CardStatistics {
pub id: u32, pub id: Id,
pub word_id: u32, pub word_id: Id,
pub set_id: u32, pub set_id: Id,
pub last_open: DateTime<Utc>, pub last_open: DateTime<Utc>,
pub score: u8, pub score: u8,
} }
@@ -320,8 +326,8 @@ impl DeckData {
.map(|w| state_locked.dictionary.get(*w).unwrap()) .map(|w| state_locked.dictionary.get(*w).unwrap())
.cloned() .cloned()
.collect(); .collect();
let word_ids = last_list.iter().map(|l| l.id).collect::<Vec<u32>>(); let word_ids = last_list.iter().map(|l| l.id).collect::<Vec<Id>>();
let mut index = 0; let mut index = 0;
for stat in current_set.clone() { for stat in current_set.clone() {
if !word_ids.contains(&stat.word_id) { if !word_ids.contains(&stat.word_id) {
@@ -410,7 +416,7 @@ impl DeckData {
self.settings.id, self.settings.id,
HistoryItem { HistoryItem {
timestamp: Utc::now(), timestamp: Utc::now(),
word_id: word.word_id, word_id: word.word_id.into(),
mode: WordOpenMode::Easy, mode: WordOpenMode::Easy,
before: old_score, before: old_score,
after: new_score, after: new_score,
@@ -606,7 +612,7 @@ impl WorstWordsSRSModule {
#[derive(Clone)] #[derive(Clone)]
pub struct DeckSettings { pub struct DeckSettings {
pub id: u32, pub id: Id,
pub name: String, pub name: String,
pub forward: String, pub forward: String,
pub backward: String, pub backward: String,
@@ -619,7 +625,7 @@ pub struct DeckSettings {
impl DeckSettings { impl DeckSettings {
pub(crate) fn with_name(name: String) -> DeckSettings { pub(crate) fn with_name(name: String) -> DeckSettings {
DeckSettings { DeckSettings {
id: 0, id: 0.into(),
name, name,
forward: "".to_string(), forward: "".to_string(),
backward: "".to_string(), backward: "".to_string(),
@@ -661,7 +667,7 @@ impl DeckSettings {
} }
let mut scope = Scope::new(); let mut scope = Scope::new();
scope scope
.push_constant("id", word.id) .push_constant("id", word.id.0)
.push_constant("key", word.key.clone()) .push_constant("key", word.key.clone())
.push_constant("value", word.value.clone()) .push_constant("value", word.value.clone())
.push_constant("tags", word.tags.clone()) .push_constant("tags", word.tags.clone())
@@ -691,3 +697,41 @@ impl DeckSettings {
self.forward == "speech" || self.backward == "speech" self.forward == "speech" || self.backward == "speech"
} }
} }
impl Into<Id> for u32{
fn into(self) -> Id {
Id(self)
}
}
// impl From<u32> for Id{
// fn from(id: u32) -> Id{
// Id(id)
// }
// }
impl Into<u32> for Id{
fn into(self) -> u32 {
self.0
}
}
impl Display for Id {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromSql for Id{
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(i) => Ok(Id(i as u32)),
_ => Err(FromSqlError::InvalidType),
}
}
}
impl ToSql for Id{
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.0 as i64))
}
}
+2 -2
View File
@@ -23,7 +23,7 @@ use crate::data_provider::card_sets::load_sets;
use crate::data_provider::settings::get_setting; use crate::data_provider::settings::get_setting;
use crate::data_provider::sqlite::default_connection; use crate::data_provider::sqlite::default_connection;
use crate::data_provider::words::{load_word_groups, load_words}; use crate::data_provider::words::{load_word_groups, load_words};
use crate::lang::{DeckSettings, WordData, WordGroup}; use crate::lang::{DeckSettings, Id, WordData, WordGroup};
use crate::navigation::{AppSettings, RootMessage, ScreenState}; use crate::navigation::{AppSettings, RootMessage, ScreenState};
use crate::quiz::*; use crate::quiz::*;
use chrono::NaiveDate; use chrono::NaiveDate;
@@ -85,7 +85,7 @@ pub struct AppState {
pub word_groups: Vec<WordGroup>, pub word_groups: Vec<WordGroup>,
pub connection: Connection, pub connection: Connection,
pub sync_data: AppSettings, pub sync_data: AppSettings,
pub activity: HashMap<u32, Vec<(NaiveDate, u32)>>, pub activity: HashMap<Id, Vec<(NaiveDate, u32)>>,
} }
impl Default for AppState { impl Default for AppState {
+4 -3
View File
@@ -31,6 +31,7 @@ use hashbrown::HashMap;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Instant; use std::time::Instant;
use rusqlite::Connection; use rusqlite::Connection;
use crate::lang::Id;
impl Default for ScreenState { impl Default for ScreenState {
fn default() -> Self { fn default() -> Self {
@@ -60,7 +61,7 @@ pub enum RootMessage {
RepetitionSettings(RepetitionSettingsMessage), RepetitionSettings(RepetitionSettingsMessage),
Import(ImportMessage), Import(ImportMessage),
Keyboard(Event), Keyboard(Event),
DataLoaded(HashMap<u32, Vec<(NaiveDate, u32)>>), DataLoaded(HashMap<Id, Vec<(NaiveDate, u32)>>),
None, None,
UpdateData, UpdateData,
} }
@@ -126,9 +127,9 @@ impl ScreenState {
for file in directory.read_dir().unwrap().flatten() { for file in directory.read_dir().unwrap().flatten() {
let mut vec = vec![]; let mut vec = vec![];
let history_file_name = file.file_name().into_string().unwrap(); let history_file_name = file.file_name().into_string().unwrap();
let id = history_file_name[4..history_file_name.len() - 12] let id : Id = history_file_name[4..history_file_name.len() - 12]
.parse::<u32>() .parse::<u32>()
.unwrap(); .unwrap().into();
let history = get_history_of_set(id); let history = get_history_of_set(id);
let by_date = history.chunk_by(|x, x1| { let by_date = history.chunk_by(|x, x1| {
+2 -1
View File
@@ -13,6 +13,7 @@ use iced::{Element, Fill, Task, Theme, alignment, keyboard};
use rodio::MixerDeviceSink; use rodio::MixerDeviceSink;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::lang::Id;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
pub struct RepetitionState { pub struct RepetitionState {
@@ -24,7 +25,7 @@ pub struct RepetitionState {
open: bool, open: bool,
can_play: bool, can_play: bool,
sink: Arc<MixerDeviceSink>, sink: Arc<MixerDeviceSink>,
opened: HashSet<u32>, opened: HashSet<Id>,
} }
impl NavigatedPage<RepetitionMessage> for RepetitionState { impl NavigatedPage<RepetitionMessage> for RepetitionState {
+8 -10
View File
@@ -27,14 +27,12 @@ use std::sync::{Arc, Mutex};
#[derive(Clone)] #[derive(Clone)]
pub struct RepetitionsState { pub struct RepetitionsState {
selected_set_index: Option<usize>,
correct_filters: Vec<bool>, correct_filters: Vec<bool>,
current_sets_cards_cache: HashMap<usize, (Vec<usize>, Vec<usize>)>, current_sets_cards_cache: HashMap<usize, (Vec<usize>, Vec<usize>)>,
word_id_index_map: HashMap<u32, usize>, word_id_index_map: HashMap<u32, usize>,
local_settings: HashMap<u32, DeckLocalSetting>, local_settings: HashMap<u32, DeckLocalSetting>,
view_data: Option<SetViewData>, view_data: Option<SetViewData>,
pub state: Arc<Mutex<AppState>>, state: Arc<Mutex<AppState>>,
set_names: Vec<String>
} }
impl NavigatedPage<RepetitionsMessage> for RepetitionsState { impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
@@ -89,7 +87,9 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
GoToHistory => {} GoToHistory => {}
GoToSettings => {} GoToSettings => {}
CreateSet => {} CreateSet => {
}
DeleteSet => {} DeleteSet => {}
SetName(_) => {} SetName(_) => {}
SelectSet(_) => {} SelectSet(_) => {}
@@ -209,15 +209,15 @@ impl RepetitionsState {
button("Проверить фильтр") button("Проверить фильтр")
.style(jl_button) .style(jl_button)
.on_press(TryFilter), .on_press(TryFilter),
self.count_view(&set), self.count_view(set),
] ]
.spacing(QUARTER_SPACING), .spacing(QUARTER_SPACING),
] ]
.spacing(DEFAULT_SPACING) .spacing(DEFAULT_SPACING)
} else { } else {
column![ column![
self.filled_set_data_view(&set), self.filled_set_data_view(set),
self.word_append_panel(&set), self.word_append_panel(set),
radio( radio(
"Обычный режим", "Обычный режим",
OrderMode::Default, OrderMode::Default,
@@ -430,7 +430,6 @@ impl RepetitionsState {
impl RepetitionsState { impl RepetitionsState {
fn append_all_words(&self, set: &DeckSettings) { fn append_all_words(&self, set: &DeckSettings) {
let index = self.selected_set_index.unwrap();
let cache = &self.current_sets_cards_cache[&index]; let cache = &self.current_sets_cards_cache[&index];
let mut created_set = HashSet::with_capacity(cache.0.len()); let mut created_set = HashSet::with_capacity(cache.0.len());
cache.0.iter().for_each(|c| { cache.0.iter().for_each(|c| {
@@ -461,13 +460,12 @@ impl RepetitionsState {
fn select_set(&mut self, index: usize) { fn select_set(&mut self, index: usize) {
let state = self.state.lock().unwrap(); let state = self.state.lock().unwrap();
if let Some(set) = state.card_sets.get(index) { if let Some(set) = state.card_sets.get(index) {
self.selected_set_index = Some(index);
} }
} }
fn clear_selection(&mut self) { fn clear_selection(&mut self) {
self.selected_set_index = None; self.view_data = None;
} }
} }
#[derive(Clone)] #[derive(Clone)]