Add update policy controls

This commit is contained in:
2026-05-09 04:13:41 -04:00
parent 726471ea64
commit e30fce9213
7 changed files with 212 additions and 17 deletions
+1 -1
View File
@@ -96,7 +96,7 @@ impl ElyShell {
"ely://settings/plugins" => self.render_plugins_page(snapshot, cx), "ely://settings/plugins" => self.render_plugins_page(snapshot, cx),
"ely://settings/profiles" => self.render_profiles_page(snapshot, cx), "ely://settings/profiles" => self.render_profiles_page(snapshot, cx),
"ely://settings/sync" => self.render_sync_page(snapshot, cx), "ely://settings/sync" => self.render_sync_page(snapshot, cx),
"ely://settings/updates" => self.render_updates_page(snapshot), "ely://settings/updates" => self.render_updates_page(snapshot, cx),
"ely://sync/status" => self.render_sync_page(snapshot, cx), "ely://sync/status" => self.render_sync_page(snapshot, cx),
url if super::web_surface::is_external_web_url(url) => { url if super::web_surface::is_external_web_url(url) => {
self.render_external_web_canvas(tab, snapshot, cx) self.render_external_web_canvas(tab, snapshot, cx)
@@ -2,10 +2,13 @@ use std::env;
use ely_browser_core::BrowserSnapshot; use ely_browser_core::BrowserSnapshot;
use ely_design_system::colors; use ely_design_system::colors;
use ely_domain::UpdatePolicy;
use gpui::{AnyElement, IntoElement, ParentElement, Styled, div, px, rgb}; use gpui::{AnyElement, IntoElement, ParentElement, Styled, div, px, rgb};
use gpui_component::{IconName, StyledExt, scroll::ScrollableElement}; use gpui_component::{
IconName, Selectable, Sizable, StyledExt,
use crate::brand::SYNC_SERVICE_NAME; button::{Button, ButtonVariants},
scroll::ScrollableElement,
};
use super::{ElyShell, render_canvas_surface}; use super::{ElyShell, render_canvas_surface};
@@ -17,7 +20,11 @@ const RELEASE_MANIFEST_CACHE: &str = "release_manifest_cache";
const RELEASE_INTEGRITY: &str = "SHA-256 + Ed25519"; const RELEASE_INTEGRITY: &str = "SHA-256 + Ed25519";
impl ElyShell { impl ElyShell {
pub(super) fn render_updates_page(&mut self, snapshot: &BrowserSnapshot) -> AnyElement { pub(super) fn render_updates_page(
&mut self,
snapshot: &BrowserSnapshot,
cx: &mut gpui::Context<Self>,
) -> AnyElement {
render_canvas_surface( render_canvas_surface(
div() div()
.size_full() .size_full()
@@ -26,7 +33,8 @@ impl ElyShell {
.flex_col() .flex_col()
.gap_5() .gap_5()
.child(render_updates_header(snapshot)) .child(render_updates_header(snapshot))
.child(render_updates_summary()) .child(render_updates_summary(snapshot.update_policy, cx))
.child(render_update_policy_rows(snapshot.update_policy, cx))
.child(render_update_contract_rows()), .child(render_update_contract_rows()),
) )
} }
@@ -67,7 +75,10 @@ fn render_updates_header(snapshot: &BrowserSnapshot) -> AnyElement {
.into_any_element() .into_any_element()
} }
fn render_updates_summary() -> AnyElement { fn render_updates_summary(
update_policy: UpdatePolicy,
cx: &mut gpui::Context<ElyShell>,
) -> AnyElement {
div() div()
.rounded_md() .rounded_md()
.border_1() .border_1()
@@ -99,15 +110,120 @@ fn render_updates_summary() -> AnyElement {
.text_color(rgb(colors::INK)) .text_color(rgb(colors::INK))
.child("Release Manifest Contract"), .child("Release Manifest Contract"),
) )
.child(div().text_xs().truncate().text_color(rgb(colors::MUTED)).child( .child(
format!( div()
"{} target through {SYNC_SERVICE_NAME} release APIs", .text_xs()
release_target() .truncate()
.text_color(rgb(colors::MUTED))
.child(update_policy.detail()),
), ),
)),
), ),
) )
.child(div().text_xs().font_semibold().text_color(rgb(colors::SUCCESS)).child(APP_VERSION)) .child(
div()
.flex()
.items_center()
.gap_2()
.child(
div()
.text_xs()
.font_semibold()
.text_color(rgb(colors::SUCCESS))
.child(update_policy.name()),
)
.child(
Button::new("reset-update-settings")
.ghost()
.xsmall()
.icon(IconName::Undo2)
.label("Reset")
.tooltip("Restore Update Defaults")
.on_click(cx.listener(|shell, _, _, cx| {
shell.reset_update_settings(cx);
})),
),
)
.into_any_element()
}
fn render_update_policy_rows(
active_policy: UpdatePolicy,
cx: &mut gpui::Context<ElyShell>,
) -> AnyElement {
div()
.flex()
.flex_col()
.border_t_1()
.border_color(rgb(colors::HAIRLINE))
.children(
UpdatePolicy::ALL
.iter()
.copied()
.enumerate()
.map(|(index, policy)| render_update_policy_row(index, policy, active_policy, cx)),
)
.into_any_element()
}
fn render_update_policy_row(
index: usize,
policy: UpdatePolicy,
active_policy: UpdatePolicy,
cx: &mut gpui::Context<ElyShell>,
) -> AnyElement {
let selected = policy == active_policy;
div()
.py_3()
.border_b_1()
.border_color(rgb(colors::HAIRLINE))
.flex()
.items_center()
.justify_between()
.gap_4()
.child(
div()
.min_w_0()
.flex()
.items_center()
.gap_3()
.child(
div().text_color(rgb(policy_icon_color(selected))).child(policy_icon(selected)),
)
.child(
div()
.min_w_0()
.flex()
.flex_col()
.gap_1()
.child(
div()
.text_sm()
.font_semibold()
.truncate()
.text_color(rgb(colors::INK))
.child(policy.name()),
)
.child(
div()
.text_xs()
.truncate()
.text_color(rgb(colors::MUTED))
.child(policy.detail()),
),
),
)
.child(
Button::new(("update-policy", index))
.ghost()
.xsmall()
.selected(selected)
.label(policy_button_label(selected))
.tooltip(policy.name())
.on_click(cx.listener(move |shell, _, _, cx| {
shell.set_update_policy(policy, cx);
})),
)
.into_any_element() .into_any_element()
} }
@@ -149,6 +265,18 @@ fn render_update_contract_rows() -> AnyElement {
.into_any_element() .into_any_element()
} }
fn policy_icon(selected: bool) -> IconName {
if selected { IconName::CircleCheck } else { IconName::LoaderCircle }
}
fn policy_icon_color(selected: bool) -> u32 {
if selected { colors::PRIMARY } else { colors::MUTED_SOFT }
}
fn policy_button_label(selected: bool) -> &'static str {
if selected { "Active" } else { "Select" }
}
fn update_row( fn update_row(
icon: IconName, icon: IconName,
label: &'static str, label: &'static str,
+19 -1
View File
@@ -1,6 +1,6 @@
use ely_domain::{ use ely_domain::{
ArchivePolicy, DownloadPolicy, FavoriteLimit, HistoryRecordingPolicy, NewTabDestination, ArchivePolicy, DownloadPolicy, FavoriteLimit, HistoryRecordingPolicy, NewTabDestination,
ProfileId, ProfileSyncPolicy, SearchEngine, SyncObjectKind, SyncObjectPolicy, ProfileId, ProfileSyncPolicy, SearchEngine, SyncObjectKind, SyncObjectPolicy, UpdatePolicy,
}; };
use gpui::Context; use gpui::Context;
@@ -151,6 +151,24 @@ impl ElyShell {
} }
} }
pub(super) fn set_update_policy(
&mut self,
update_policy: UpdatePolicy,
cx: &mut Context<Self>,
) {
if let ShellState::Ready(core) = &mut self.state {
core.set_update_policy(update_policy);
cx.notify();
}
}
pub(super) fn reset_update_settings(&mut self, cx: &mut Context<Self>) {
if let ShellState::Ready(core) = &mut self.state {
core.reset_update_settings();
cx.notify();
}
}
pub(super) fn archive_idle_tabs_now(&mut self, cx: &mut Context<Self>) { pub(super) fn archive_idle_tabs_now(&mut self, cx: &mut Context<Self>) {
if let ShellState::Ready(core) = &mut self.state if let ShellState::Ready(core) = &mut self.state
&& core.archive_idle_tabs(std::time::SystemTime::now()).is_ok() && core.archive_idle_tabs(std::time::SystemTime::now()).is_ok()
+18 -1
View File
@@ -5,7 +5,7 @@ use ely_domain::{
DownloadEntry, DownloadPolicy, FavoriteLimit, HistoryEntry, HistoryRecordingPolicy, DownloadEntry, DownloadPolicy, FavoriteLimit, HistoryEntry, HistoryRecordingPolicy,
NewTabDestination, NoteEntry, Profile, ProfileId, ProfileKind, ReadingListEntry, SearchEngine, NewTabDestination, NoteEntry, Profile, ProfileId, ProfileKind, ReadingListEntry, SearchEngine,
SitePermissionAuditEvent, SitePermissionEntry, Space, SpaceId, SplitLayout, SyncStatus, SitePermissionAuditEvent, SitePermissionEntry, Space, SpaceId, SplitLayout, SyncStatus,
TabGroup, TabId, UrlText, TabGroup, TabId, UpdatePolicy, UrlText,
}; };
use crate::{CoreError, navigation::tab_title}; use crate::{CoreError, navigation::tab_title};
@@ -113,6 +113,7 @@ pub struct BrowserSnapshot {
pub new_tab_destination: NewTabDestination, pub new_tab_destination: NewTabDestination,
pub history_recording_policy: HistoryRecordingPolicy, pub history_recording_policy: HistoryRecordingPolicy,
pub favorite_limit: FavoriteLimit, pub favorite_limit: FavoriteLimit,
pub update_policy: UpdatePolicy,
pub command_query: String, pub command_query: String,
} }
@@ -144,6 +145,7 @@ pub struct BrowserCore {
new_tab_destination: NewTabDestination, new_tab_destination: NewTabDestination,
history_recording_policy: HistoryRecordingPolicy, history_recording_policy: HistoryRecordingPolicy,
favorite_limit: FavoriteLimit, favorite_limit: FavoriteLimit,
update_policy: UpdatePolicy,
sync_object_policies: SyncObjectPolicies, sync_object_policies: SyncObjectPolicies,
command_query: String, command_query: String,
} }
@@ -189,6 +191,7 @@ impl BrowserCore {
new_tab_destination, new_tab_destination,
history_recording_policy: HistoryRecordingPolicy::default(), history_recording_policy: HistoryRecordingPolicy::default(),
favorite_limit: FavoriteLimit::default(), favorite_limit: FavoriteLimit::default(),
update_policy: UpdatePolicy::default(),
sync_object_policies: SyncObjectPolicies::default(), sync_object_policies: SyncObjectPolicies::default(),
spaces: vec![space], spaces: vec![space],
profiles: vec![profile], profiles: vec![profile],
@@ -348,6 +351,19 @@ impl BrowserCore {
self.favorite_limit self.favorite_limit
} }
pub fn set_update_policy(&mut self, update_policy: UpdatePolicy) {
self.update_policy = update_policy;
}
pub fn reset_update_settings(&mut self) {
self.set_update_policy(UpdatePolicy::default());
}
#[must_use]
pub fn update_policy(&self) -> UpdatePolicy {
self.update_policy
}
pub fn set_command_query(&mut self, query: impl Into<String>) { pub fn set_command_query(&mut self, query: impl Into<String>) {
self.command_query = query.into(); self.command_query = query.into();
} }
@@ -393,6 +409,7 @@ impl BrowserCore {
new_tab_destination: self.new_tab_destination, new_tab_destination: self.new_tab_destination,
history_recording_policy: self.history_recording_policy, history_recording_policy: self.history_recording_policy,
favorite_limit: self.favorite_limit, favorite_limit: self.favorite_limit,
update_policy: self.update_policy,
command_query: self.command_query.clone(), command_query: self.command_query.clone(),
}) })
} }
@@ -4,7 +4,7 @@ use ely_browser_core::{BrowserCore, InitialBrowserConfig};
use ely_domain::{ use ely_domain::{
ArchivePolicy, DEFAULT_SIDEBAR_WIDTH_PX, DownloadPolicy, FavoriteLimit, HistoryRecordingPolicy, ArchivePolicy, DEFAULT_SIDEBAR_WIDTH_PX, DownloadPolicy, FavoriteLimit, HistoryRecordingPolicy,
NewTabDestination, ProfileKind, ProfileSyncPolicy, SearchEngine, SyncObjectKind, NewTabDestination, ProfileKind, ProfileSyncPolicy, SearchEngine, SyncObjectKind,
SyncObjectPolicy, SyncObjectPolicy, UpdatePolicy,
}; };
#[test] #[test]
@@ -47,6 +47,10 @@ fn section_resets_restore_settings_defaults() -> Result<(), Box<dyn Error>> {
core.set_sync_object_policy(SyncObjectKind::Tabs, SyncObjectPolicy::Paused); core.set_sync_object_policy(SyncObjectKind::Tabs, SyncObjectPolicy::Paused);
core.reset_sync_settings(); core.reset_sync_settings();
assert_eq!(core.sync_object_policy(SyncObjectKind::Tabs), SyncObjectPolicy::Enabled); assert_eq!(core.sync_object_policy(SyncObjectKind::Tabs), SyncObjectPolicy::Enabled);
core.set_update_policy(UpdatePolicy::Manual);
core.reset_update_settings();
assert_eq!(core.snapshot()?.update_policy, UpdatePolicy::Automatic);
Ok(()) Ok(())
} }
+2
View File
@@ -19,6 +19,7 @@ mod split;
mod sync; mod sync;
mod tab; mod tab;
mod tab_group; mod tab_group;
mod update;
mod url_text; mod url_text;
pub use archive::{ArchiveSource, ArchivedTab}; pub use archive::{ArchiveSource, ArchivedTab};
@@ -57,4 +58,5 @@ pub use sync::{
}; };
pub use tab::{BrowserTab, TabFlags, TabState}; pub use tab::{BrowserTab, TabFlags, TabState};
pub use tab_group::TabGroup; pub use tab_group::TabGroup;
pub use update::UpdatePolicy;
pub use url_text::UrlText; pub use url_text::UrlText;
+26
View File
@@ -0,0 +1,26 @@
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum UpdatePolicy {
#[default]
Automatic,
Manual,
}
impl UpdatePolicy {
pub const ALL: &[Self] = &[Self::Automatic, Self::Manual];
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::Automatic => "Automatic",
Self::Manual => "Manual",
}
}
#[must_use]
pub fn detail(self) -> &'static str {
match self {
Self::Automatic => "Use release manifests as the automatic update source.",
Self::Manual => "Keep release manifest checks user-initiated.",
}
}
}