Search functionality
This commit is contained in:
39
3/coursework/src/Cargo.lock
generated
39
3/coursework/src/Cargo.lock
generated
@ -516,9 +516,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.12"
|
version = "1.2.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2"
|
checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
@ -2311,9 +2311,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.3"
|
version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
|
checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
@ -3257,6 +3257,7 @@ dependencies = [
|
|||||||
"data",
|
"data",
|
||||||
"iced",
|
"iced",
|
||||||
"service",
|
"service",
|
||||||
|
"strum",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3892,6 +3893,28 @@ dependencies = [
|
|||||||
"unicode-properties",
|
"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]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
@ -4118,9 +4141,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.23"
|
version = "0.22.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee"
|
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
@ -4999,9 +5022,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.1"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f"
|
checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -8,6 +8,7 @@ data = { path = "data" }
|
|||||||
service = { path = "service" }
|
service = { path = "service" }
|
||||||
|
|
||||||
iced = { version = "0.13.1", features = ["tokio", "lazy"] }
|
iced = { version = "0.13.1", features = ["tokio", "lazy"] }
|
||||||
|
strum = { version = "0.27.0", features = ["derive"] }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
103
3/coursework/src/assets/init/1-data.sql
Normal file
103
3/coursework/src/assets/init/1-data.sql
Normal file
@ -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');
|
@ -111,8 +111,8 @@ where
|
|||||||
base_name: row.try_get("base_name")?,
|
base_name: row.try_get("base_name")?,
|
||||||
url: row.try_get("url")?,
|
url: row.try_get("url")?,
|
||||||
description: row.try_get("description")?,
|
description: row.try_get("description")?,
|
||||||
submitter_id: row.try_get("submitter_id")?,
|
// submitter_id: row.try_get("submitter_id")?,
|
||||||
submitter_name: row.try_get("submitter_name")?,
|
// submitter_name: row.try_get("submitter_name")?,
|
||||||
updated_at: row.try_get("updated_at")?,
|
updated_at: row.try_get("updated_at")?,
|
||||||
created_at: row.try_get("created_at")?,
|
created_at: row.try_get("created_at")?,
|
||||||
});
|
});
|
||||||
|
@ -37,8 +37,8 @@ pub struct Entry {
|
|||||||
pub base_name: Box<str>,
|
pub base_name: Box<str>,
|
||||||
pub url: Option<Box<str>>,
|
pub url: Option<Box<str>>,
|
||||||
pub description: Box<str>,
|
pub description: Box<str>,
|
||||||
pub submitter_id: u64,
|
// pub submitter_id: u64,
|
||||||
pub submitter_name: Box<str>,
|
// pub submitter_name: Box<str>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use crate::input::Input;
|
use crate::input::Input;
|
||||||
use crate::widget::centerbox;
|
use crate::widget::centerbox;
|
||||||
use service::authentication;
|
|
||||||
use service::{
|
use service::{
|
||||||
Authenticated, AuthenticationContract,
|
Authenticated, AuthenticationContract,
|
||||||
authentication::{Error, LoginData, Result},
|
authentication::{self, Error, LoginData, Result},
|
||||||
};
|
};
|
||||||
|
|
||||||
use iced::futures::lock::Mutex;
|
use iced::futures::lock::Mutex;
|
||||||
|
@ -101,13 +101,12 @@ impl<S: AuthenticationContract + 'static> Register<S> {
|
|||||||
Message::EmailSubmitted => return Some(self.password.focus().into()),
|
Message::EmailSubmitted => return Some(self.password.focus().into()),
|
||||||
Message::PasswordSubmitted if self.password.critical() => (),
|
Message::PasswordSubmitted if self.password.critical() => (),
|
||||||
Message::PasswordSubmitted => return Some(self.repeat.focus().into()),
|
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() =>
|
||||||
Message::RegisterPressed | Message::RepeatSubmitted => {
|
{
|
||||||
if self.repeat.error().is_some() {
|
|
||||||
return Some(self.repeat.focus().into());
|
return Some(self.repeat.focus().into());
|
||||||
}
|
}
|
||||||
|
Message::RegisterPressed | Message::RepeatSubmitted => {
|
||||||
let register_data = RegisterData {
|
let register_data = RegisterData {
|
||||||
name: match self.name.submit() {
|
name: match self.name.submit() {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
@ -146,7 +145,7 @@ impl<S: AuthenticationContract + 'static> Register<S> {
|
|||||||
Message::RequestResult(r) => match &*r {
|
Message::RequestResult(r) => match &*r {
|
||||||
Ok(a) => {
|
Ok(a) => {
|
||||||
self.state = State::Success;
|
self.state = State::Success;
|
||||||
return Some(Event::Authenticated(a.clone()))
|
return Some(Event::Authenticated(a.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -1,47 +1,45 @@
|
|||||||
// mod main_window;
|
|
||||||
// mod authentication;
|
|
||||||
mod authentication;
|
mod authentication;
|
||||||
mod input;
|
mod input;
|
||||||
|
mod search;
|
||||||
|
//mod statistics;
|
||||||
mod widget;
|
mod widget;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::authentication::Authentication;
|
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::{
|
use iced::{
|
||||||
Element, Subscription, Task, Theme,
|
Element, Subscription, Task, Theme,
|
||||||
futures::lock::Mutex,
|
futures::lock::Mutex,
|
||||||
widget::{center, row},
|
widget::{center, row},
|
||||||
window,
|
window,
|
||||||
};
|
};
|
||||||
use service::{AuthenticationAdapter, AuthenticationService};
|
use service::{
|
||||||
|
Authenticated, AuthenticationAdapter, AuthenticationService, SearchAdapter, SearchService,
|
||||||
// #[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)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Repository {
|
struct Repository {
|
||||||
scale_factor: f64,
|
scale_factor: f64,
|
||||||
main_id: window::Id,
|
main_id: window::Id,
|
||||||
|
screen: Screen,
|
||||||
|
|
||||||
|
authenticated: Option<Authenticated>,
|
||||||
|
|
||||||
|
search: Search<SearchService<SearchAdapter<MySqlPool, SqlxPool, MySqlSearchAdapter>>>,
|
||||||
authentication: Authentication<
|
authentication: Authentication<
|
||||||
AuthenticationService<AuthenticationAdapter<MySqlPool, SqlxPool, MySqlUserAdapter>>,
|
AuthenticationService<AuthenticationAdapter<MySqlPool, SqlxPool, MySqlUserAdapter>>,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Screen {
|
||||||
|
Search,
|
||||||
|
// Statistics,
|
||||||
|
Authentication,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Message {
|
enum Message {
|
||||||
ScaleUp,
|
ScaleUp,
|
||||||
@ -49,8 +47,8 @@ enum Message {
|
|||||||
WindowOpened(window::Id),
|
WindowOpened(window::Id),
|
||||||
WindowClosed(window::Id),
|
WindowClosed(window::Id),
|
||||||
|
|
||||||
|
Search(search::Message),
|
||||||
Authentecation(authentication::Message),
|
Authentecation(authentication::Message),
|
||||||
// MainWindow(main_window::Message),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Repository {
|
impl Repository {
|
||||||
@ -70,10 +68,20 @@ impl Repository {
|
|||||||
AuthenticationAdapter::new(pool.clone()),
|
AuthenticationAdapter::new(pool.clone()),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
let search_service = Arc::new(Mutex::new(SearchService::new(SearchAdapter::new(
|
||||||
|
pool.clone(),
|
||||||
|
))));
|
||||||
|
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
scale_factor: 1.4,
|
|
||||||
main_id,
|
main_id,
|
||||||
|
scale_factor: 1.4,
|
||||||
|
screen: Screen::Search,
|
||||||
|
|
||||||
|
|
||||||
|
authenticated: None,
|
||||||
|
|
||||||
|
search: Search::new(search_service),
|
||||||
authentication: Authentication::new(auth_service),
|
authentication: Authentication::new(auth_service),
|
||||||
},
|
},
|
||||||
Task::batch([
|
Task::batch([
|
||||||
@ -98,29 +106,48 @@ impl Repository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Authentecation(message) => {
|
Message::Authentecation(message) => {
|
||||||
if let Some(action) = self.authentication.update(message) {
|
if let Some(event) = self.authentication.update(message) {
|
||||||
match action {
|
match event {
|
||||||
authentication::Event::Task(task) => {
|
authentication::Event::Task(task) => {
|
||||||
return task.map(Message::Authentecation);
|
return task.map(Message::Authentecation);
|
||||||
}
|
}
|
||||||
authentication::Event::Authenticated(authenticated) => {
|
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()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, id: window::Id) -> Element<Message> {
|
fn view(&self, id: window::Id) -> Element<Message> {
|
||||||
if self.main_id == id {
|
if self.main_id == id {
|
||||||
// self.main_window.view().map(Message::MainWindow)
|
match self.screen {
|
||||||
self.authentication.view().map(Message::Authentecation)
|
Screen::Search => self.search.view().map(Message::Search),
|
||||||
|
Screen::Authentication => self.authentication.view().map(Message::Authentecation),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
center(row!["This window is unknown.", "It may be closed."]).into()
|
center(row!["This window is unknown.", "It may be closed."]).into()
|
||||||
}
|
}
|
||||||
@ -151,3 +178,19 @@ impl Repository {
|
|||||||
Theme::TokyoNight
|
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)
|
||||||
|
}
|
||||||
|
333
3/coursework/src/src/search.rs
Normal file
333
3/coursework/src/src/search.rs
Normal file
@ -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<S> {
|
||||||
|
input: Input<search::Search>,
|
||||||
|
mode: Mode,
|
||||||
|
order: Order,
|
||||||
|
ascending: bool,
|
||||||
|
exact: bool,
|
||||||
|
limit: u8,
|
||||||
|
|
||||||
|
state: State,
|
||||||
|
service: Arc<Mutex<S>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Mode> 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<Order> 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<str>),
|
||||||
|
|
||||||
|
RequestResult(Arc<Result<Vec<Entry>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
Task(Task<Message>),
|
||||||
|
OpenPackage(u64),
|
||||||
|
OpenBase(u64),
|
||||||
|
OpenURL(Box<str>),
|
||||||
|
}
|
||||||
|
impl From<Task<Message>> for Event {
|
||||||
|
fn from(value: Task<Message>) -> Self {
|
||||||
|
Self::Task(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
struct Table(Vec<Entry>);
|
||||||
|
|
||||||
|
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<S: SearchContract + 'static> Search<S> {
|
||||||
|
pub fn new(service: Arc<Mutex<S>>) -> 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<Message> {
|
||||||
|
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<Event> {
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ pub fn scroll<'a, Message: 'a>(content: impl Into<Element<'a, Message>>) -> Elem
|
|||||||
|
|
||||||
/// Clickable url
|
/// Clickable url
|
||||||
pub fn url<'a, Message: Clone + 'a>(txt: &impl ToString, msg: Message) -> Element<'a, Message> {
|
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 {
|
pub mod tip {
|
||||||
|
Reference in New Issue
Block a user