diff --git a/3/coursework/src/Cargo.lock b/3/coursework/src/Cargo.lock index 696bd6a..e1c9c5b 100644 --- a/3/coursework/src/Cargo.lock +++ b/3/coursework/src/Cargo.lock @@ -516,9 +516,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.12" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" +checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" dependencies = [ "jobserver", "libc", @@ -2311,9 +2311,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" dependencies = [ "adler2", "simd-adler32", @@ -3257,6 +3257,7 @@ dependencies = [ "data", "iced", "service", + "strum", ] [[package]] @@ -3892,6 +3893,28 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strum" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1475c515a4f03a8a7129bb5228b81a781a86cb0b3fbbc19e1c556d491a401f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9688894b43459159c82bfa5a5fa0435c19cbe3c9b427fa1dd7b1ce0c279b18a7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.98", +] + [[package]] name = "subtle" version = "2.6.1" @@ -4118,9 +4141,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.22.23" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "toml_datetime", @@ -4999,9 +5022,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" +checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" dependencies = [ "memchr", ] diff --git a/3/coursework/src/Cargo.toml b/3/coursework/src/Cargo.toml index 3a63616..e12dad9 100644 --- a/3/coursework/src/Cargo.toml +++ b/3/coursework/src/Cargo.toml @@ -8,6 +8,7 @@ data = { path = "data" } service = { path = "service" } iced = { version = "0.13.1", features = ["tokio", "lazy"] } +strum = { version = "0.27.0", features = ["derive"] } [workspace] resolver = "2" diff --git a/3/coursework/src/assets/init/init.sql b/3/coursework/src/assets/init/0-init.sql similarity index 100% rename from 3/coursework/src/assets/init/init.sql rename to 3/coursework/src/assets/init/0-init.sql diff --git a/3/coursework/src/assets/init/1-data.sql b/3/coursework/src/assets/init/1-data.sql new file mode 100644 index 0000000..e7ea4b4 --- /dev/null +++ b/3/coursework/src/assets/init/1-data.sql @@ -0,0 +1,103 @@ +-- Insert Users +INSERT INTO Users (name, email, password, last_used) VALUES +('alice', 'alice@example.com', 'password123', NOW()), +('bob', 'bob@example.com', 'securepass', NOW()), +('charlie', 'charlie@example.com', 'charliepwd', NOW()), +('dave', 'dave@example.com', 'davepass', NOW()), +('eve', 'eve@example.com', 'evepwd', NOW()), +('frank', 'frank@example.com', 'frankpass', NOW()), +('grace', 'grace@example.com', 'gracepwd', NOW()), +('heidi', 'heidi@example.com', 'heidipwd', NOW()), +('ivan', 'ivan@example.com', 'ivanpass', NOW()), +('judy', 'judy@example.com', 'judypass', NOW()), +('mallory', 'mallory@example.com', 'mallorypwd', NOW()), +('oscar', 'oscar@example.com', 'oscarpass', NOW()), +('peggy', 'peggy@example.com', 'peggypwd', NOW()), +('trent', 'trent@example.com', 'trentpass', NOW()), +('victor', 'victor@example.com', 'victorpwd', NOW()); + +-- Insert PackageBases +INSERT INTO PackageBases (name, description) VALUES +('libcore', 'Core system libraries'), +('webframework', 'A modern web framework'), +('dataproc', 'Data processing toolkit'), +('authmodule', 'Authentication and authorization module'), +('networkstack', 'Networking utilities and stack'), +('uikit', 'UI Kit for building interfaces'), +('cryptoengine', 'Cryptographic library'), +('dbconnector', 'Database connectivity drivers'), +('imageproc', 'Image processing library'), +('audiokit', 'Audio toolkit'), +('videokit', 'Video processing toolkit'), +('mlcore', 'Machine Learning core library'), +('analyticspro', 'Advanced analytics toolkit'), +('monitoragent', 'System monitoring agent'), +('filesystem', 'Filesystem utilities'); + +-- Assign Roles to Users for PackageBases +INSERT INTO PackageBaseUserRoles (base, user, role, comment) VALUES +(1, 1, 1, 'Original submitter'), +(1, 2, 2, 'Packager for latest release'), +(2, 3, 3, 'Maintains stability'), +(2, 4, 4, 'Flags issues'), +(3, 5, 1, 'Initial submission'), +(3, 6, 3, 'Lead maintainer'), +(4, 7, 2, 'Core packager'), +(5, 8, 1, 'Submitted new version'), +(6, 9, 4, 'Flagged for performance issues'), +(7, 10, 3, 'Maintainer for security fixes'), +(8, 11, 2, 'Driver package manager'), +(9, 12, 1, 'Original contributor'), +(10, 13, 3, 'Maintains core features'), +(11, 14, 4, 'Reported critical bug'), +(12, 15, 2, 'Optimized build process'); + +-- Insert Packages +INSERT INTO Packages (base, name, version, description, url) VALUES +(1, 'libcore-utils', '1.0.0', 'Utilities for libcore', 'http://example.com/libcore-utils'), +(1, 'libcore-extended', '1.1.0', 'Extended functionalities', 'http://example.com/libcore-extended'), +(2, 'webframework-api', '2.0.0', 'REST API module', 'http://example.com/webframework-api'), +(2, 'webframework-cli', '2.1.0', 'Command-line tools', 'http://example.com/webframework-cli'), +(3, 'dataproc-engine', '3.0.1', 'Data processing engine', 'http://example.com/dataproc-engine'), +(4, 'authmodule-oauth', '4.2.0', 'OAuth module', 'http://example.com/authmodule-oauth'), +(5, 'networkstack-core', '5.5.0', 'Core network stack', 'http://example.com/networkstack-core'), +(6, 'uikit-designer', '6.0.3', 'UI designer toolkit', 'http://example.com/uikit-designer'), +(7, 'cryptoengine-hash', '7.1.1', 'Hash algorithms', 'http://example.com/cryptoengine-hash'), +(8, 'dbconnector-mysql', '8.0.0', 'MySQL connector', 'http://example.com/dbconnector-mysql'), +(9, 'imageproc-filters', '9.3.0', 'Image filters library', 'http://example.com/imageproc-filters'), +(10, 'audiokit-mixer', '10.2.1', 'Audio mixing toolkit', 'http://example.com/audiokit-mixer'), +(11, 'videokit-stream', '11.4.0', 'Video streaming tools', 'http://example.com/videokit-stream'), +(12, 'mlcore-algo', '12.0.2', 'ML algorithms', 'http://example.com/mlcore-algo'), +(13, 'analyticspro-dashboard', '13.5.1', 'Analytics dashboard', 'http://example.com/analyticspro-dashboard'); + +-- Insert PackageDependencies +INSERT INTO PackageDependencies (arch, requirement, description, package, dependency_type, dependency_package_name) VALUES +('x86_64', '>=1.0.0', 'Core dependency', 3, 1, 'libcore-utils'), +('x86_64', '>=2.0.0', 'Required for API', 4, 2, 'webframework-api'), +('arm64', '>=3.0.1', 'Optional analytics', 5, 4, 'analyticspro-dashboard'), +('x86_64', '>=5.5.0', 'Network stack dependency', 6, 1, 'networkstack-core'), +('x86_64', '>=4.2.0', 'Authentication module', 7, 1, 'authmodule-oauth'), +('x86_64', NULL, 'Database driver', 8, 1, 'dbconnector-mysql'), +('arm64', NULL, 'Machine learning algorithms', 9, 3, 'mlcore-algo'), +('x86_64', '>=6.0.3', 'UI designer toolkit', 10, 1, 'uikit-designer'), +('x86_64', NULL, 'Audio toolkit dependency', 11, 2, 'audiokit-mixer'), +('x86_64', '>=7.1.1', 'Hash functions', 12, 1, 'cryptoengine-hash'), +('arm64', NULL, 'Video streaming tools', 13, 4, 'videokit-stream'), +('x86_64', '>=9.3.0', 'Image filters', 14, 1, 'imageproc-filters'), +('x86_64', NULL, 'System monitoring agent', 15, 2, 'monitoragent'); + +-- Insert PackageRelations +INSERT INTO PackageRelations (arch, requirement, package, relation_type, relation_package_name) VALUES +('x86_64', '>=1.0.0', 3, 1, 'legacy-web-api'), -- conflicts +('x86_64', NULL, 4, 2, 'web-cli-tools'), -- provides +('arm64', NULL, 5, 3, 'old-dataproc'), -- replaces +('x86_64', '>=5.0.0', 6, 1, 'net-tools-legacy'), +('x86_64', NULL, 7, 2, 'crypto-lib'), +('x86_64', '>=4.0.0', 8, 3, 'db-driver-old'), +('arm64', NULL, 9, 1, 'imgproc-v1'), +('x86_64', NULL, 10, 2, 'audio-tools'), +('x86_64', '>=7.0.0', 11, 3, 'video-kit-old'), +('x86_64', NULL, 12, 1, 'ml-core-legacy'), +('x86_64', '>=6.0.0', 13, 2, 'analytics-pro-tools'), +('x86_64', NULL, 14, 3, 'monitor-agent-v1'), +('x86_64', '>=9.0.0', 15, 1, 'filesystem-old'); diff --git a/3/coursework/src/data/src/adapter/mysql/search.rs b/3/coursework/src/data/src/adapter/mysql/search.rs index 0dbc964..03ce524 100644 --- a/3/coursework/src/data/src/adapter/mysql/search.rs +++ b/3/coursework/src/data/src/adapter/mysql/search.rs @@ -111,8 +111,8 @@ where base_name: row.try_get("base_name")?, url: row.try_get("url")?, description: row.try_get("description")?, - submitter_id: row.try_get("submitter_id")?, - submitter_name: row.try_get("submitter_name")?, + // submitter_id: row.try_get("submitter_id")?, + // submitter_name: row.try_get("submitter_name")?, updated_at: row.try_get("updated_at")?, created_at: row.try_get("created_at")?, }); diff --git a/3/coursework/src/data/src/port/search.rs b/3/coursework/src/data/src/port/search.rs index 8a8f33a..2af96f3 100644 --- a/3/coursework/src/data/src/port/search.rs +++ b/3/coursework/src/data/src/port/search.rs @@ -37,8 +37,8 @@ pub struct Entry { pub base_name: Box, pub url: Option>, pub description: Box, - pub submitter_id: u64, - pub submitter_name: Box, + // pub submitter_id: u64, + // pub submitter_name: Box, pub updated_at: DateTime, pub created_at: DateTime, } diff --git a/3/coursework/src/src/authentication/login.rs b/3/coursework/src/src/authentication/login.rs index e5990fc..df0f216 100644 --- a/3/coursework/src/src/authentication/login.rs +++ b/3/coursework/src/src/authentication/login.rs @@ -1,9 +1,8 @@ use crate::input::Input; use crate::widget::centerbox; -use service::authentication; use service::{ Authenticated, AuthenticationContract, - authentication::{Error, LoginData, Result}, + authentication::{self, Error, LoginData, Result}, }; use iced::futures::lock::Mutex; diff --git a/3/coursework/src/src/authentication/register.rs b/3/coursework/src/src/authentication/register.rs index b1a78e4..6615775 100644 --- a/3/coursework/src/src/authentication/register.rs +++ b/3/coursework/src/src/authentication/register.rs @@ -101,13 +101,12 @@ impl Register { Message::EmailSubmitted => return Some(self.password.focus().into()), Message::PasswordSubmitted if self.password.critical() => (), Message::PasswordSubmitted => return Some(self.repeat.focus().into()), - Message::RepeatSubmitted if self.repeat.error().is_some() => (), - + Message::RegisterPressed | Message::RepeatSubmitted + if self.repeat.error().is_some() => + { + return Some(self.repeat.focus().into()); + } Message::RegisterPressed | Message::RepeatSubmitted => { - if self.repeat.error().is_some() { - return Some(self.repeat.focus().into()); - } - let register_data = RegisterData { name: match self.name.submit() { Ok(x) => x, @@ -146,7 +145,7 @@ impl Register { Message::RequestResult(r) => match &*r { Ok(a) => { self.state = State::Success; - return Some(Event::Authenticated(a.clone())) + return Some(Event::Authenticated(a.clone())); } Err(e) => { diff --git a/3/coursework/src/src/main.rs b/3/coursework/src/src/main.rs index 407fc40..0fe0b6d 100644 --- a/3/coursework/src/src/main.rs +++ b/3/coursework/src/src/main.rs @@ -1,47 +1,45 @@ -// mod main_window; -// mod authentication; mod authentication; mod input; +mod search; +//mod statistics; mod widget; use std::sync::Arc; use crate::authentication::Authentication; -// use crate::main_window::MainWindow; +use crate::search::Search; +//use crate::statistics::Statistics; -use data::{MySqlPool, MySqlUserAdapter, SqlxPool}; +use data::{MySqlPool, MySqlSearchAdapter, MySqlUserAdapter, SqlxPool}; use iced::{ Element, Subscription, Task, Theme, futures::lock::Mutex, widget::{center, row}, window, }; -use service::{AuthenticationAdapter, AuthenticationService}; - -// #[macro_export] -macro_rules! log { - ($($arg:tt)*) => { - #[cfg(debug_assertions)] - println!($($arg)*) - }; -} - -fn main() -> iced::Result { - iced::daemon(Repository::title, Repository::update, Repository::view) - .subscription(Repository::subscription) - .scale_factor(Repository::scale_factor) - .theme(Repository::theme) - .run_with(Repository::new) -} +use service::{ + Authenticated, AuthenticationAdapter, AuthenticationService, SearchAdapter, SearchService, +}; struct Repository { scale_factor: f64, main_id: window::Id, + screen: Screen, + + authenticated: Option, + + search: Search>>, authentication: Authentication< AuthenticationService>, >, } +enum Screen { + Search, + // Statistics, + Authentication, +} + #[derive(Debug)] enum Message { ScaleUp, @@ -49,8 +47,8 @@ enum Message { WindowOpened(window::Id), WindowClosed(window::Id), + Search(search::Message), Authentecation(authentication::Message), - // MainWindow(main_window::Message), } impl Repository { @@ -70,10 +68,20 @@ impl Repository { AuthenticationAdapter::new(pool.clone()), ))); + let search_service = Arc::new(Mutex::new(SearchService::new(SearchAdapter::new( + pool.clone(), + )))); + ( Self { - scale_factor: 1.4, main_id, + scale_factor: 1.4, + screen: Screen::Search, + + + authenticated: None, + + search: Search::new(search_service), authentication: Authentication::new(auth_service), }, Task::batch([ @@ -98,29 +106,48 @@ impl Repository { } } Message::Authentecation(message) => { - if let Some(action) = self.authentication.update(message) { - match action { + if let Some(event) = self.authentication.update(message) { + match event { authentication::Event::Task(task) => { return task.map(Message::Authentecation); } authentication::Event::Authenticated(authenticated) => { - log!("authenticated via login {:#?}", authenticated); + log!("authenticated as {:#?}", authenticated); + self.authenticated = Some(authenticated); + self.screen = Screen::Search; } } } - } // - // Message::MainWindow(message) => match self.main_window.update(message) { - // main_window::Action::None => (), - // main_window::Action::Task(task) => return task.map(Message::MainWindow), - // }, + } + Message::Search(m) => { + if let Some(event) = self.search.update(m) { + match event { + search::Event::Task(task) => { + return task.map(Message::Search); + } + search::Event::OpenPackage(id) => { + log!("opening package {id}") + } + search::Event::OpenBase(id) => { + log!("opening package base {id}") + } + search::Event::OpenURL(url) => { + log!("opening url {url}") + } + } + } + } } + Task::none() } fn view(&self, id: window::Id) -> Element { if self.main_id == id { - // self.main_window.view().map(Message::MainWindow) - self.authentication.view().map(Message::Authentecation) + match self.screen { + Screen::Search => self.search.view().map(Message::Search), + Screen::Authentication => self.authentication.view().map(Message::Authentecation), + } } else { center(row!["This window is unknown.", "It may be closed."]).into() } @@ -151,3 +178,19 @@ impl Repository { Theme::TokyoNight } } + +#[macro_export] +macro_rules! log { + ($($arg:tt)*) => { + #[cfg(debug_assertions)] + println!($($arg)*) + }; +} + +fn main() -> iced::Result { + iced::daemon(Repository::title, Repository::update, Repository::view) + .subscription(Repository::subscription) + .scale_factor(Repository::scale_factor) + .theme(Repository::theme) + .run_with(Repository::new) +} diff --git a/3/coursework/src/src/search.rs b/3/coursework/src/src/search.rs new file mode 100644 index 0000000..bb84c15 --- /dev/null +++ b/3/coursework/src/src/search.rs @@ -0,0 +1,333 @@ +use crate::input::Input; +use crate::widget::{scroll, tip, url}; + +use iced::Length::Shrink; +use service::search::Data; +use service::{ + SearchContract, + search::{self, Entry, Result}, +}; + +use iced::widget::{Column, button, checkbox, column, container, lazy, pick_list, row, text}; +use iced::{Element, Length::Fill, Task, futures::lock::Mutex}; +use std::sync::Arc; +use strum::{Display, VariantArray}; + +pub struct Search { + input: Input, + mode: Mode, + order: Order, + ascending: bool, + exact: bool, + limit: u8, + + state: State, + service: Arc>, +} + +#[derive(Default)] +enum State { + #[default] + None, + Searching, + // Aborted, + Table(Table), + Error(String), +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Display, VariantArray)] +pub enum Mode { + Url, + Name, + #[strum(to_string = "Package Base")] + PackageBase, + Description, + #[strum(to_string = "Base description")] + BaseDescription, + #[default] + #[strum(to_string = "Name and Description")] + NameAndDescription, + User, + Flagger, + Packager, + Submitter, + Maintainer, +} +impl From for search::Mode { + fn from(value: Mode) -> Self { + match value { + Mode::Url => Self::Url, + Mode::Name => Self::Name, + Mode::PackageBase => Self::PackageBase, + Mode::Description => Self::Description, + Mode::BaseDescription => Self::BaseDescription, + Mode::NameAndDescription => Self::NameAndDescription, + Mode::User => Self::User, + Mode::Flagger => Self::Flagger, + Mode::Packager => Self::Packager, + Mode::Submitter => Self::Submitter, + Mode::Maintainer => Self::Maintainer, + } + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Display, VariantArray)] +pub enum Order { + Name, + Version, + #[strum(to_string = "Base Name")] + BaseName, + // Submitter, + #[default] + #[strum(to_string = "Last update")] + UpdatedAt, + #[strum(to_string = "Created time")] + CreatedAt, +} +impl From for search::Order { + fn from(value: Order) -> Self { + match value { + Order::Name => Self::Name, + Order::Version => Self::Version, + Order::BaseName => Self::BaseName, + Order::UpdatedAt => Self::UpdatedAt, + Order::CreatedAt => Self::CreatedAt, + } + } +} + +#[derive(Debug, Clone)] +pub enum Message { + // Search bar + Reset, + Search, + SearchChanged(String), + ModePicked(Mode), + OrderPicked(Order), + AscendingToggled(bool), + ExactToggled(bool), + ShowEntriesPicked(u8), + // Table + PackagePressed(u64), + BasePressed(u64), + URLPressed(Box), + + RequestResult(Arc>>), +} + +pub enum Event { + Task(Task), + OpenPackage(u64), + OpenBase(u64), + OpenURL(Box), +} +impl From> for Event { + fn from(value: Task) -> Self { + Self::Task(value) + } +} + +#[derive(Debug, Hash)] +struct Table(Vec); + +impl Table { + pub fn view(&self) -> Element<'static, Message> { + let mut table: Vec<_> = [ + "Package", // 0 + "Version", // 1 + "Base", // 2 + "URL", // 3 + "Description", // 4 + "Last Updated", // 5 + "Created", // 6 + ] + .into_iter() + .map(|s| { + let mut v = Vec::with_capacity(self.0.len()); + v.push(s.into()); + v.push("".into()); + v + }) + .collect(); + + for entry in &self.0 { + table[0].push(url(&entry.name, Message::PackagePressed(entry.id))); + table[1].push(text(entry.version.to_string()).into()); + table[2].push(url(&entry.base_name, Message::BasePressed(entry.base_id))); + table[3].push( + entry + .url + .as_ref() + .map_or("-".into(), |s| url(&"link", Message::URLPressed(s.clone()))), + ); + table[4].push(text(entry.description.to_string()).into()); + table[5].push(text(entry.updated_at.to_string()).into()); + table[6].push(text(entry.created_at.to_string()).into()); + // table[5].push(Element::from(column( entry .maintainers .iter() .map(|(id, s)| url(s, Message::UserPressed(*id))),))); + } + + scroll( + row(table + .into_iter() + .map(|v| Column::from_vec(v).spacing(5).into())) + .spacing(20) + .padding(30), + ) + } +} + +impl Search { + pub fn new(service: Arc>) -> Self { + Self { + input: Input::new("search_input"), + mode: Mode::NameAndDescription, + order: Order::UpdatedAt, + ascending: false, + exact: false, + limit: 25, + state: State::default(), + service, + } + } + + pub fn view(&self) -> Element { + let search_bar = container(scroll( + column![ + row![ + self.input + .view("Search") + .on_input(Message::SearchChanged) + .on_submit(Message::Search), + tip( + button("Go").on_press(Message::Search), + "Perform the search", + tip::Position::Bottom, + ), + ] + .spacing(10), + row![ + tip( + button("Reset").on_press(Message::Reset), + "Reset the search bar", + tip::Position::Bottom, + ), + tip( + pick_list(Mode::VARIANTS, Some(&self.mode), Message::ModePicked), + "Search mode", + tip::Position::Bottom, + ), + tip( + pick_list(Order::VARIANTS, Some(&self.order), Message::OrderPicked), + "Field used to sort the results", + tip::Position::Bottom, + ), + tip( + checkbox("Exact", self.exact).on_toggle(Message::ExactToggled), + "Exact search", + tip::Position::Bottom, + ), + tip( + checkbox("Ascending", self.ascending).on_toggle(Message::AscendingToggled), + "Sort order of results", + tip::Position::Bottom, + ), + tip( + pick_list( + [25, 50, 75, 100], + Some(self.limit), + Message::ShowEntriesPicked + ), + "Number of results to show", + tip::Position::Bottom, + ), + ] + .spacing(10) + ] + .padding(20) + .width(750) + .spacing(10), + )) + .center_x(Fill); + + column![ + search_bar, + match &self.state { + State::None => Element::from(""), + State::Searching => "Searching...".into(), + // State::Aborted => "Aborted".into(), + State::Error(e) => text(e).into(), + State::Table(table) => container(lazy(table, |t| t.view())).center_x(Fill).into(), + } + ] + .into() + } + + pub fn update(&mut self, message: Message) -> Option { + match message { + Message::SearchChanged(s) => self.input.update(s), + Message::ModePicked(mode) => self.mode = mode, + Message::OrderPicked(order) => self.order = order, + Message::AscendingToggled(b) => self.ascending = b, + Message::ExactToggled(b) => self.exact = b, + Message::ShowEntriesPicked(x) => self.limit = x, + Message::Reset => { + let state = std::mem::take(&mut self.state); + *self = Self::new(self.service.clone()); + self.state = state; + } + + Message::PackagePressed(id) => return Some(Event::OpenPackage(id)), + Message::BasePressed(id) => return Some(Event::OpenBase(id)), + Message::URLPressed(url) => return Some(Event::OpenURL(url)), + + Message::Search => { + let search_data = Data { + mode: self.mode.into(), + order: self.order.into(), + search: match self.input.submit() { + Ok(x) => x, + Err(t) => return Some(t.into()), + }, + limit: self.limit.into(), + exact: self.exact, + ascending: self.ascending, + }; + + self.state = State::Searching; + let arc = self.service.clone(); + + return Some( + Task::perform( + async move { + let Some(service) = arc.try_lock() else { + return Err("other search request is being performed".into()); + }; + service.search(search_data).await + }, + |r| Message::RequestResult(Arc::new(r)), + ) + .into(), + ); + } + + Message::RequestResult(r) => match &*r { + Ok(v) => self.state = State::Table(Table(v.clone())), + Err(e) => self.state = State::Error(e.to_string()), + }, + } + + None + } + + pub fn title(&self) -> String { + let errors = [self.input.error(), self.input.warning()]; + let error = errors.into_iter().flatten().next(); + + match &self.state { + State::None => error.map_or_else(|| "Search".into(), Into::into), + State::Searching => "Searching...".into(), + State::Table(_) => "Displaying search results".into(), + State::Error(e) => e.into(), + } + } +} diff --git a/3/coursework/src/src/widget.rs b/3/coursework/src/src/widget.rs index d30c20f..efb94fa 100644 --- a/3/coursework/src/src/widget.rs +++ b/3/coursework/src/src/widget.rs @@ -26,7 +26,7 @@ pub fn scroll<'a, Message: 'a>(content: impl Into>) -> Elem /// Clickable url pub fn url<'a, Message: Clone + 'a>(txt: &impl ToString, msg: Message) -> Element<'a, Message> { - Element::from(mouse_area(text(txt.to_string()).color(color!(0xBB_B6_DF))).on_press(msg)) + Element::from(mouse_area(text(txt.to_string()).color(color!(0x00_BB_FF))).on_press(msg)) } pub mod tip {