feat: added a don't show again option to the update prompt (#723)
This commit is contained in:
parent
4aaf974f04
commit
2c43aa16ba
3 changed files with 209 additions and 33 deletions
133
packages/oneclient_app/src/components/checkbox.rs
Normal file
133
packages/oneclient_app/src/components/checkbox.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use freya::{
|
||||
animation::{AnimColor, AnimNum, AnimatedValue, Ease, Function, use_animation_transition},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
components::{Icon, IconType},
|
||||
theme::colors,
|
||||
ui,
|
||||
};
|
||||
|
||||
const BOX_SIZE: f32 = 18.;
|
||||
const MARK_SIZE: f32 = 12.;
|
||||
const TIME: u64 = 180;
|
||||
|
||||
pub fn checkbox_labeled(value: State<bool>, text: impl Into<Box<str>>) -> impl IntoElement {
|
||||
let mut v = value;
|
||||
Check {
|
||||
value,
|
||||
label: Some(text.into()),
|
||||
on_press: (move |()| v.toggle()).into(),
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct Check {
|
||||
value: State<bool>,
|
||||
label: Option<Box<str>>,
|
||||
on_press: EventHandler<()>,
|
||||
disabled: bool,
|
||||
}
|
||||
|
||||
impl Component for Check {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let value = self.value;
|
||||
let on_press = self.on_press.clone();
|
||||
let checked = *value.read();
|
||||
let disabled = self.disabled;
|
||||
|
||||
let a11y_id = use_a11y();
|
||||
let focus = use_focus(a11y_id);
|
||||
|
||||
let box_border = if focus().is_focused() && !disabled {
|
||||
colors::fg_primary()
|
||||
} else if checked && !disabled {
|
||||
colors::brand()
|
||||
} else {
|
||||
colors::component_border()
|
||||
};
|
||||
|
||||
let background = use_animation_transition(value, |_, checked| {
|
||||
if checked {
|
||||
AnimColor::new(colors::component_bg(), colors::brand())
|
||||
} else {
|
||||
AnimColor::new(colors::brand(), colors::component_bg())
|
||||
}
|
||||
.time(TIME)
|
||||
.function(Function::Expo)
|
||||
.ease(Ease::Out)
|
||||
});
|
||||
|
||||
let mark = use_animation_transition(value, |_, checked| {
|
||||
let scale = AnimNum::new(0.5, 1.)
|
||||
.time(TIME)
|
||||
.function(Function::Expo)
|
||||
.ease(Ease::Out);
|
||||
let opacity = AnimNum::new(0., 1.)
|
||||
.time(TIME)
|
||||
.function(Function::Expo)
|
||||
.ease(Ease::Out);
|
||||
|
||||
if checked {
|
||||
(scale, opacity)
|
||||
} else {
|
||||
(scale.into_reversed(), opacity.into_reversed())
|
||||
}
|
||||
});
|
||||
|
||||
let (mark_scale, mark_opacity) = {
|
||||
let mark = mark.read();
|
||||
(mark.0.value(), mark.1.value())
|
||||
};
|
||||
|
||||
rect()
|
||||
.horizontal()
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(8.)
|
||||
.a11y_id(a11y_id)
|
||||
.a11y_focusable(!disabled)
|
||||
.a11y_role(AccessibilityRole::CheckBox)
|
||||
.maybe(!disabled, |el| {
|
||||
el.cursor(CursorIcon::Pointer).on_press(move |_| {
|
||||
on_press.call(());
|
||||
})
|
||||
})
|
||||
.child(
|
||||
rect()
|
||||
.width(Size::px(BOX_SIZE))
|
||||
.height(Size::px(BOX_SIZE))
|
||||
.corner_radius(CornerRadius::new_all(5.))
|
||||
.main_align(Alignment::Center)
|
||||
.cross_align(Alignment::Center)
|
||||
.background(if disabled {
|
||||
colors::component_bg_disabled()
|
||||
} else {
|
||||
background.read().value()
|
||||
})
|
||||
.border(ui::border_all_color(1., box_border))
|
||||
.maybe_child((checked || mark_opacity > 0.).then(|| {
|
||||
rect().opacity(mark_opacity).scale(mark_scale).child(
|
||||
Icon::new(IconType::Check)
|
||||
.size(MARK_SIZE)
|
||||
.color(if disabled {
|
||||
colors::fg_secondary()
|
||||
} else {
|
||||
Color::WHITE
|
||||
}),
|
||||
)
|
||||
})),
|
||||
)
|
||||
.maybe_child(self.label.as_ref().map(|text| {
|
||||
label()
|
||||
.text(text.to_string())
|
||||
.font_size(12.)
|
||||
.color(if disabled {
|
||||
colors::fg_secondary()
|
||||
} else {
|
||||
colors::fg_primary()
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ mod active_cluster_panel;
|
|||
mod avatar;
|
||||
mod button;
|
||||
mod charts;
|
||||
mod checkbox;
|
||||
mod cluster_landscape_art;
|
||||
mod cluster_update_popup;
|
||||
mod context_menu;
|
||||
|
|
@ -46,6 +47,7 @@ pub use active_cluster_panel::ActiveClusterPanel;
|
|||
pub use avatar::Avatar;
|
||||
pub use button::{Button, ButtonSize, ButtonVariant, link_button, open_folder_button};
|
||||
pub use charts::{BarChart, PieChart, ValueUnit, slice_color};
|
||||
pub use checkbox::checkbox_labeled;
|
||||
pub use cluster_landscape_art::ClusterLandscapeArt;
|
||||
pub use cluster_update_popup::ClusterUpdatePopup;
|
||||
pub use context_menu::ContextMenu;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use freya::prelude::*;
|
||||
use oneclient_common::{PackageUpdateMode, Patch};
|
||||
use oneclient_content::packages::{CachedPackageMeta, ProviderId};
|
||||
use oneclient_core::BrowserPackageUpdate;
|
||||
use oneclient_core::{BrowserPackageUpdate, ProfileUpdate};
|
||||
use oneclient_db::models::ClusterId;
|
||||
|
||||
use crate::components::{Button, Dropdown, Icon, IconType, OverlayPopup, ScrollArea};
|
||||
use crate::components::{Button, Dropdown, Icon, IconType, OverlayPopup, ScrollArea, checkbox_labeled};
|
||||
use crate::hooks::{
|
||||
package_meta_batch, use_dispatch, use_notifications_snapshot, use_package_meta_batch,
|
||||
};
|
||||
|
|
@ -63,6 +64,7 @@ impl Component for PackageUpdatePopup {
|
|||
|
||||
// Absent entries default to Update so groups arriving mid-modal need no reconciliation
|
||||
let choices = use_state(HashMap::<RowKey, RowChoice>::new);
|
||||
let mut dont_show_again = use_state(|| false);
|
||||
|
||||
let groups = snapshot.package_updates.clone();
|
||||
|
||||
|
|
@ -83,6 +85,8 @@ impl Component for PackageUpdatePopup {
|
|||
}
|
||||
|
||||
let Some(groups) = groups.filter(|groups| !groups.is_empty()) else {
|
||||
// The popup stays mounted, so the tick must not survive into the next prompt
|
||||
dont_show_again.set_if_modified(false);
|
||||
return rect().into_element();
|
||||
};
|
||||
|
||||
|
|
@ -95,7 +99,7 @@ impl Component for PackageUpdatePopup {
|
|||
.width(Size::window_percent(100.))
|
||||
.height(Size::window_percent(100.))
|
||||
.center()
|
||||
.child(dialog(&groups, &meta, dispatch, choices)),
|
||||
.child(dialog(&groups, &meta, dispatch, choices, dont_show_again)),
|
||||
)
|
||||
.into_element()
|
||||
}
|
||||
|
|
@ -113,6 +117,7 @@ fn dialog(
|
|||
meta: &MetaMap,
|
||||
dispatch: crate::Actions,
|
||||
choices: State<HashMap<RowKey, RowChoice>>,
|
||||
dont_show_again: State<bool>,
|
||||
) -> impl IntoElement {
|
||||
rect()
|
||||
.vertical()
|
||||
|
|
@ -130,7 +135,7 @@ fn dialog(
|
|||
0.,
|
||||
Color::from_argb(150, 0, 0, 0),
|
||||
)))
|
||||
.child(content(groups, meta, dispatch, choices))
|
||||
.child(content(groups, meta, dispatch, choices, dont_show_again))
|
||||
}
|
||||
|
||||
fn content(
|
||||
|
|
@ -138,10 +143,13 @@ fn content(
|
|||
meta: &MetaMap,
|
||||
dispatch: crate::Actions,
|
||||
choices: State<HashMap<RowKey, RowChoice>>,
|
||||
dont_show_again: State<bool>,
|
||||
) -> impl IntoElement {
|
||||
let dismiss = dispatch.clone();
|
||||
let proceed_dispatch = dispatch.clone();
|
||||
let dsa_dispatch = dispatch.clone();
|
||||
let total: usize = groups.iter().map(|group| group.packages.len()).sum();
|
||||
let clusters: Vec<ClusterId> = groups.iter().map(|group| group.cluster_id).collect();
|
||||
|
||||
let subtitle = match groups {
|
||||
[only] => format!(
|
||||
|
|
@ -194,40 +202,73 @@ fn content(
|
|||
.horizontal()
|
||||
.width(Size::fill())
|
||||
.cross_align(Alignment::Center)
|
||||
.main_align(Alignment::End)
|
||||
.main_align(Alignment::SpaceBetween)
|
||||
.spacing(8.)
|
||||
.child(
|
||||
Button::new()
|
||||
.ghost()
|
||||
.on_press(move |_| dismiss.close_package_updates())
|
||||
.text("Cancel"),
|
||||
rect()
|
||||
.direction(Direction::Horizontal)
|
||||
.main_align(Alignment::Start)
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(6.)
|
||||
.child(
|
||||
checkbox_labeled(dont_show_again, "Don't show again?")
|
||||
)
|
||||
)
|
||||
.child(
|
||||
Button::new()
|
||||
.primary()
|
||||
.on_press(move |_| {
|
||||
let answers = choices.read();
|
||||
for update in &all {
|
||||
match answers
|
||||
.get(&row_key(update))
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
{
|
||||
RowChoice::Update => {
|
||||
proceed_dispatch.apply_package_update(update.clone());
|
||||
rect()
|
||||
.width(Size::fill())
|
||||
.direction(Direction::Horizontal)
|
||||
.cross_align(Alignment::Center)
|
||||
.main_align(Alignment::End)
|
||||
.child(
|
||||
Button::new()
|
||||
.ghost()
|
||||
.on_press(move |_| dismiss.close_package_updates())
|
||||
.text("Cancel"),
|
||||
)
|
||||
.child(
|
||||
Button::new()
|
||||
.primary()
|
||||
.on_press(move |_| {
|
||||
if *dont_show_again.peek() {
|
||||
// A cluster override outranks the global one, so the prompt
|
||||
// is only silenced by writing the cluster that raised it
|
||||
for cluster_id in &clusters {
|
||||
dsa_dispatch.update_cluster_profile(
|
||||
*cluster_id,
|
||||
ProfileUpdate {
|
||||
browser_update_mode: Patch::Set(
|
||||
PackageUpdateMode::Skip,
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
RowChoice::Skip => {}
|
||||
RowChoice::SkipVersion => proceed_dispatch
|
||||
.skip_package_update(
|
||||
update.cluster_id,
|
||||
update.hash.clone(),
|
||||
),
|
||||
}
|
||||
}
|
||||
proceed_dispatch.close_package_updates();
|
||||
})
|
||||
.child(Icon::new(IconType::DownloadCloud02).size(15.))
|
||||
.text("Proceed"),
|
||||
|
||||
let answers = choices.read();
|
||||
for update in &all {
|
||||
match answers
|
||||
.get(&row_key(update))
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
{
|
||||
RowChoice::Update => {
|
||||
proceed_dispatch.apply_package_update(update.clone());
|
||||
}
|
||||
RowChoice::Skip => {}
|
||||
RowChoice::SkipVersion => proceed_dispatch
|
||||
.skip_package_update(
|
||||
update.cluster_id,
|
||||
update.hash.clone(),
|
||||
),
|
||||
}
|
||||
}
|
||||
proceed_dispatch.close_package_updates();
|
||||
})
|
||||
.child(Icon::new(IconType::DownloadCloud02).size(15.))
|
||||
.text("Proceed"),
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue