From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 1F0251FF186 for ; Fri, 29 Aug 2025 14:23:44 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 67A232D604; Fri, 29 Aug 2025 14:23:54 +0200 (CEST) From: Dominik Csapak To: yew-devel@lists.proxmox.com Date: Fri, 29 Aug 2025 14:21:05 +0200 Message-ID: <20250829122320.2053866-1-d.csapak@proxmox.com> X-Mailer: git-send-email 2.47.2 MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.021 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [yew-devel] [PATCH yew-comp] rrd time frame selector: seperate time frame from mode X-BeenThere: yew-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Yew framework devel list at Proxmox List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Yew framework devel list at Proxmox Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: yew-devel-bounces@lists.proxmox.com Sender: "yew-devel" by splitting the one combobox with many entries into a combobox for the timeframe (hour, day, etc.) and a segmented button for maximum/average. This makes the combobox less cluttered and sill retains all the information. While at it, use the `tr` macro to prepare the values for translation. Signed-off-by: Dominik Csapak --- i tried using radio buttons for the mode, but this looked weird when using inline in pdm gui. Using a similar mechanism as the task modes seemed better to me. I thought about implementing the timeframe as a slider, but this make the UX worse IMHO, since one does not know what values exist before selecting (we don't have a range/slider component yet where the values are labeled) src/rrd_timeframe_selector.rs | 125 +++++++++++++++++----------------- 1 file changed, 61 insertions(+), 64 deletions(-) diff --git a/src/rrd_timeframe_selector.rs b/src/rrd_timeframe_selector.rs index 5758a04..36ded09 100644 --- a/src/rrd_timeframe_selector.rs +++ b/src/rrd_timeframe_selector.rs @@ -6,10 +6,12 @@ use yew::html::IntoEventCallback; use yew::prelude::*; use yew::virtual_dom::{VComp, VNode}; -use proxmox_rrd_api_types as rrd_types; +use proxmox_rrd_api_types::{self as rrd_types, RrdMode, RrdTimeframe}; +use pwt::css::{AlignItems, ColorScheme}; use pwt::prelude::*; use pwt::state::local_storage; use pwt::widget::form::Combobox; +use pwt::widget::{Button, Row, SegmentedButton}; use pwt_macros::builder; /// Combobox for selecting the theme density. @@ -145,24 +147,20 @@ pub struct PwtRRDTimeframeSelector { pub enum Msg { SetRRDTimeframe(String), + SetRRDMode(RrdMode), } -fn display_value(v: &AttrValue) -> &str { +fn display_value(v: &AttrValue) -> Html { match v.as_str() { - "hour-AVERAGE" => "Hour (average)", - "hour-MAX" => "Hour (maximum)", - "day-AVERAGE" => "Day (average)", - "day-MAX" => "Day (maximum)", - "week-AVERAGE" => "Week (average)", - "week-MAX" => "Week (maximum)", - "month-AVERAGE" => "Month (average)", - "month-MAX" => "Month (maximum)", - "year-AVERAGE" => "Year (average)", - "year-MAX" => "Year (maximum)", - "decade-AVERAGE" => "Decade (average)", - "decade-MAX" => "Decade (maximum)", - _ => v, - } + "hour" => tr!("Hour"), + "day" => tr!("Day"), + "week" => tr!("Week"), + "month" => tr!("Month"), + "year" => tr!("Year"), + "decade" => tr!("Decade"), + _ => v.to_string(), + } + .into() } impl Component for PwtRRDTimeframeSelector { @@ -170,31 +168,13 @@ impl Component for PwtRRDTimeframeSelector { type Properties = RRDTimeframeSelector; fn create(_ctx: &Context) -> Self { - use rrd_types::RrdMode::*; - use rrd_types::RrdTimeframe::*; - - let timeframe = RRDTimeframe::load(); - - let values = [ - RRDTimeframe::new(Hour, Average), - RRDTimeframe::new(Hour, Max), - RRDTimeframe::new(Day, Average), - RRDTimeframe::new(Day, Max), - RRDTimeframe::new(Week, Average), - RRDTimeframe::new(Week, Max), - RRDTimeframe::new(Month, Average), - RRDTimeframe::new(Month, Max), - RRDTimeframe::new(Year, Average), - RRDTimeframe::new(Year, Max), - RRDTimeframe::new(Decade, Average), - RRDTimeframe::new(Decade, Max), - ] - .iter() - .map(|v| AttrValue::from(v.serialize())) - .collect(); + let values = ["hour", "day", "week", "month", "year", "decade"] + .into_iter() + .map(|v| v.into()) + .collect(); Self { - timeframe, + timeframe: RRDTimeframe::load(), items: Rc::new(values), } } @@ -203,38 +183,55 @@ impl Component for PwtRRDTimeframeSelector { let props = ctx.props(); match msg { Msg::SetRRDTimeframe(timeframe_str) => { - if let Ok(timeframe) = timeframe_str.as_str().parse::() { - timeframe.store(); - self.timeframe = timeframe; - if let Some(on_change) = &props.on_change { - on_change.emit(timeframe); - } + if let Ok(timeframe) = timeframe_str.as_str().parse::() { + self.timeframe.timeframe = timeframe; + self.timeframe.store(); } - true } + Msg::SetRRDMode(mode) => { + self.timeframe.mode = mode; + self.timeframe.store(); + } + } + if let Some(on_change) = &props.on_change { + on_change.emit(self.timeframe); } + true } fn view(&self, ctx: &Context) -> Html { let props = ctx.props(); - - Combobox::new() - .required(true) - .min_width(150) - .class(props.class.clone()) - .default(self.timeframe.serialize()) - .items(self.items.clone()) - .on_change(ctx.link().callback(Msg::SetRRDTimeframe)) - .render_value(|v: &AttrValue| { - html! {display_value(v)} - }) - .show_filter(false) - // Note: This is just for completeness. Not used because we do not show the filter... - .filter(|item: &AttrValue, query: &str| { - display_value(item) - .to_lowercase() - .contains(&query.to_lowercase()) - }) + let average = self.timeframe.mode == RrdMode::Average; + let max = self.timeframe.mode == RrdMode::Max; + + Row::new() + .class(AlignItems::Center) + .gap(2) + .with_child( + Combobox::new() + .required(true) + .min_width(100) + .class(props.class.clone()) + .default(self.timeframe.timeframe.to_string()) + .items(self.items.clone()) + .on_change(ctx.link().callback(Msg::SetRRDTimeframe)) + .render_value(display_value), + ) + .with_child( + SegmentedButton::new() + .with_button( + Button::new(tr!("Maximum")) + .on_activate(ctx.link().callback(|_| Msg::SetRRDMode(RrdMode::Max))) + .class(max.then_some(ColorScheme::Primary)) + .pressed(max), + ) + .with_button( + Button::new(tr!("Average")) + .on_activate(ctx.link().callback(|_| Msg::SetRRDMode(RrdMode::Average))) + .class(average.then_some(ColorScheme::Primary)) + .pressed(average), + ), + ) .into() } } -- 2.47.2 _______________________________________________ yew-devel mailing list yew-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/yew-devel