From: Dominik Csapak <d.csapak@proxmox.com>
To: yew-devel@lists.proxmox.com
Subject: [yew-devel] [PATCH yew-comp] rrd time frame selector: seperate time frame from mode
Date: Fri, 29 Aug 2025 14:21:05 +0200 [thread overview]
Message-ID: <20250829122320.2053866-1-d.csapak@proxmox.com> (raw)
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 <d.csapak@proxmox.com>
---
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>) -> 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::<RRDTimeframe>() {
- 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::<RrdTimeframe>() {
+ 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<Self>) -> 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
reply other threads:[~2025-08-29 12:23 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250829122320.2053866-1-d.csapak@proxmox.com \
--to=d.csapak@proxmox.com \
--cc=yew-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.