diff --git a/data/src/url.rs b/data/src/url.rs index cdc19f388..8cfa507f2 100644 --- a/data/src/url.rs +++ b/data/src/url.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use log::warn; use regex::Regex; use crate::{config, theme, Server}; @@ -15,6 +16,7 @@ pub enum Url { url: String, colors: theme::Colors, }, + Unknown(String), } impl std::fmt::Display for Url { @@ -23,7 +25,7 @@ impl std::fmt::Display for Url { f, "{}", match self { - Url::ServerConnect { url, .. } | Url::Theme { url, .. } => url, + Url::ServerConnect { url, .. } | Url::Theme { url, .. } | Url::Unknown(url) => url, } ) } @@ -40,38 +42,48 @@ impl Url { } impl FromStr for Url { - type Err = Error; + type Err = (); fn from_str(s: &str) -> Result { - let url = s.parse::()?; - - match url.scheme().to_lowercase().as_str() { - "irc" | "ircs" => { - let config = parse_server_config(&url).ok_or(Error::ParseServer)?; - let server = generate_server_name(config.server.as_str()); - let url = url.into(); - - Ok(Self::ServerConnect { - url, - server: server.into(), - config, - }) - } - "halloy" if url.path() == "/theme" => { - let (_, encoded) = url - .query_pairs() - .find(|(key, _)| key == "e") - .ok_or(Error::MissingQueryPair)?; - - let colors = theme::Colors::decode_base64(&encoded)?; - - Ok(Self::Theme { - url: url.into(), - colors, - }) - } - _ => Err(Error::Unsupported), + let url = s.parse::().map_err(|_| ())?; + + if ["irc", "ircs", "halloy"].contains(&url.scheme()) { + Ok(parse(url.clone()) + .inspect_err(|err| warn!("Failed to parse url {url}: {err}")) + .unwrap_or(Url::Unknown(url.to_string()))) + } else { + Err(()) + } + } +} + +fn parse(url: url::Url) -> Result { + match url.scheme().to_lowercase().as_str() { + "irc" | "ircs" => { + let config = parse_server_config(&url).ok_or(Error::ParseServer)?; + let server = generate_server_name(config.server.as_str()); + let url = url.into(); + + Ok(Url::ServerConnect { + url, + server: server.into(), + config, + }) + } + "halloy" if url.path() == "/theme" => { + let (_, encoded) = url + .query_pairs() + .find(|(key, _)| key == "e") + .ok_or(Error::MissingQueryPair)?; + + let colors = theme::Colors::decode_base64(&encoded)?; + + Ok(Url::Theme { + url: url.into(), + colors, + }) } + _ => Err(Error::Unknown), } } @@ -143,8 +155,8 @@ pub enum Error { ParseUrl(#[from] url::ParseError), #[error("can't convert url to a valid server")] ParseServer, - #[error("unsupported route")] - Unsupported, + #[error("unknown route")] + Unknown, #[error("missing query pair")] MissingQueryPair, #[error("failed to parse encoded theme: {0}")] diff --git a/src/main.rs b/src/main.rs index 7714db8f1..9ca2903b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -262,6 +262,9 @@ impl Halloy { .map(Message::Dashboard); } } + data::Url::Unknown(url) => { + log::warn!("Received unknown url: {url}"); + } } Task::none() diff --git a/src/screen/dashboard/theme_editor.rs b/src/screen/dashboard/theme_editor.rs index 484d2342e..f17a844f4 100644 --- a/src/screen/dashboard/theme_editor.rs +++ b/src/screen/dashboard/theme_editor.rs @@ -44,7 +44,7 @@ pub struct ThemeEditor { pub id: window::Id, combo_box: combo_box::State, component: Component, - hex_input: String, + hex_input: Option, save_result: Option, copied: bool, } @@ -67,8 +67,10 @@ impl ThemeEditor { Self { id, combo_box: combo_box::State::new(components().collect()), - component: Component::General(General::Background), - hex_input: String::default(), + // Defaulting to general / background is confusing + // since picker is same color as background + component: Component::Text(Text::Primary), + hex_input: None, save_result: None, copied: false, }, @@ -85,7 +87,7 @@ impl ThemeEditor { ) -> (Task, Option) { match message { Message::Color(color) => { - self.hex_input.clear(); + self.hex_input = None; let mut colors = *theme.colors(); @@ -94,21 +96,21 @@ impl ThemeEditor { *theme = theme.preview(data::Theme::new("Custom Theme".into(), colors)); } Message::Component(component) => { - self.hex_input.clear(); + self.hex_input = None; self.combo_box = combo_box::State::new(components().collect()); self.component = component } Message::HexInput(input) => { - self.hex_input = input; - - if let Some(color) = theme::hex_to_color(&self.hex_input) { + if let Some(color) = theme::hex_to_color(&input) { let mut colors = *theme.colors(); self.component.update(&mut colors, Some(color)); *theme = theme.preview(data::Theme::new("Custom Theme".into(), colors)); } + + self.hex_input = Some(input); } Message::Save => { let task = async move { @@ -134,7 +136,7 @@ impl ThemeEditor { return (Task::none(), Some(Event::Close)); } Message::Revert => { - self.hex_input.clear(); + self.hex_input = None; let mut colors = *theme.selected().colors(); let original = self.component.color(&colors); @@ -144,7 +146,7 @@ impl ThemeEditor { *theme = theme.preview(data::Theme::new("Custom Theme".into(), colors)); } Message::Clear => { - self.hex_input.clear(); + self.hex_input = None; let mut colors = *theme.colors(); @@ -222,17 +224,26 @@ impl ThemeEditor { Message::Component, ); - let is_input_valid = - self.hex_input.is_empty() || theme::hex_to_color(&self.hex_input).is_some(); - let hex_input = text_input(&theme::color_to_hex(color), &self.hex_input) - .on_input(Message::HexInput) - .style(move |theme, status| { - if is_input_valid { - theme::text_input::primary(theme, status) - } else { - theme::text_input::error(theme, status) - } - }); + let is_input_valid = self.hex_input.is_none() + || self + .hex_input + .as_deref() + .and_then(theme::hex_to_color) + .is_some(); + let hex_input = text_input( + "", + self.hex_input + .as_deref() + .unwrap_or(theme::color_to_hex(color).as_str()), + ) + .on_input(Message::HexInput) + .style(move |theme, status| { + if is_input_valid { + theme::text_input::primary(theme, status) + } else { + theme::text_input::error(theme, status) + } + }); let undo = icon(icon::undo(), "Revert", Message::Revert); let clear = icon(icon::delete(), "Clear", Message::Clear); diff --git a/src/widget/color_picker.rs b/src/widget/color_picker.rs index 85060eb48..1bbc07702 100644 --- a/src/widget/color_picker.rs +++ b/src/widget/color_picker.rs @@ -436,7 +436,12 @@ fn slider<'a, Message: 'a>( let bounds = layout.bounds(); let handle = slider_handle(value, color, bounds, handle_radius); - let color = Hsva::new_srgb(color.hue, 1.0, 1.0, 1.0); + let color = match component { + // Full S/V and cycle every hue + Component::Hue => Hsva::new_srgb(0.0, 1.0, 1.0, 1.0), + // Otherwise slide should change based on color + _ => color, + }; for x in 0..bounds.width as usize { renderer.fill_quad(