[pdm-devel] [RFC pdm v2] ui: make layout more consistent using new ContentSpacer widget
Dietmar Maurer
dietmar at proxmox.com
Fri Jan 10 13:50:23 CET 2025
Signed-off-by: Dietmar Maurer <dietmar at proxmox.com>
---
Changhes in v2:
- use box shadows instead of border
- use ContentSpacer for dashboard
- use ContentSpacer for PveRemote
ui/css/crisp-yew-style.scss | 12 ++++
ui/css/desktop-yew-style.scss | 11 +++
ui/css/pdm.scss | 26 ++++++-
ui/src/administration/mod.rs | 28 ++++++--
ui/src/certificates.rs | 30 ++++++--
ui/src/configuration/mod.rs | 25 ++++---
ui/src/configuration/other/mod.rs | 9 ++-
ui/src/configuration/other/webauthn.rs | 1 -
ui/src/dashboard/mod.rs | 17 +++--
ui/src/main_menu.rs | 22 +++---
ui/src/pve/mod.rs | 7 +-
ui/src/widget/content_spacer.rs | 99 ++++++++++++++++++++++++++
ui/src/widget/mod.rs | 3 +
13 files changed, 242 insertions(+), 48 deletions(-)
create mode 100644 ui/src/widget/content_spacer.rs
diff --git a/ui/css/crisp-yew-style.scss b/ui/css/crisp-yew-style.scss
index d254b53..df8bb9c 100644
--- a/ui/css/crisp-yew-style.scss
+++ b/ui/css/crisp-yew-style.scss
@@ -1,2 +1,14 @@
@import "../pwt-assets/scss/crisp-yew-style";
@import "pdm";
+
+.proxmox-content-spacer {
+ @include color-scheme-vars("neutral");
+
+ &.proxmox-content-spacer-with-one-child {
+ padding: 0;
+ }
+
+ &.proxmox-content-spacer-with-one-child > * {
+ border: 0;
+ }
+}
diff --git a/ui/css/desktop-yew-style.scss b/ui/css/desktop-yew-style.scss
index 3d1ac0a..2fb1832 100644
--- a/ui/css/desktop-yew-style.scss
+++ b/ui/css/desktop-yew-style.scss
@@ -1,2 +1,13 @@
@import "../pwt-assets/scss/desktop-yew-style";
@import "pdm";
+
+.proxmox-content-spacer {
+ padding: var(--pwt-spacer-4);
+ gap: var(--pwt-spacer-4);
+
+ & > * {
+ border: 0px !important;
+ border-top: 1px solid var(--pwt-color-surface) !important;
+ @include elevation-box-shadow(1);
+ }
+}
diff --git a/ui/css/pdm.scss b/ui/css/pdm.scss
index 4ef22e2..6597f31 100644
--- a/ui/css/pdm.scss
+++ b/ui/css/pdm.scss
@@ -60,7 +60,27 @@
}
:root.pwt-dark-mode {
- .fa-memory, .fa-cpu {
- filter: invert(90%);
- }
+ .fa-memory,
+ .fa-cpu {
+ filter: invert(90%);
+ }
+}
+
+.proxmox-content-spacer {
+ @include color-scheme-vars("surface");
+ color: var(--pwt-color);
+ background-color: var(--pwt-color-background);
+
+ padding: var(--pwt-spacer-2);
+ gap: var(--pwt-spacer-2);
+
+ display: flex;
+ flex-direction: column;
+
+ & > * {
+ @include color-scheme-vars("neutral");
+ border: 1px solid var(--pwt-color-border);
+ color: var(--pwt-color);
+ background-color: var(--pwt-color-background);
+ }
}
diff --git a/ui/src/administration/mod.rs b/ui/src/administration/mod.rs
index 15d04ae..eeb9efa 100644
--- a/ui/src/administration/mod.rs
+++ b/ui/src/administration/mod.rs
@@ -18,6 +18,8 @@ use pwt_macros::builder;
use proxmox_yew_comp::{AptPackageManager, AptRepositories, ExistingProduct, Syslog, Tasks};
+use crate::widget::ContentSpacer;
+
#[derive(Clone, PartialEq, Properties)]
#[builder]
pub struct ServerAdministration {
@@ -73,8 +75,9 @@ impl Component for PdmServerAdministration {
.label("Updates")
.icon_class("fa fa-refresh"),
move |_| {
- AptPackageManager::new()
- .enable_upgrade(enable_upgrade)
+ ContentSpacer::new()
+ .class("pwt-flex-fill")
+ .with_child(AptPackageManager::new().enable_upgrade(enable_upgrade))
.into()
},
)
@@ -83,21 +86,36 @@ impl Component for PdmServerAdministration {
.key("repositories")
.label("Repositories")
.icon_class("fa fa-files-o"),
- |_| AptRepositories::new().product(ExistingProduct::PDM).into(),
+ |_| {
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
+ .with_child(AptRepositories::new().product(ExistingProduct::PDM))
+ .into()
+ },
)
.with_item_builder(
TabBarItem::new()
.key("syslog")
.label("Syslog")
.icon_class("fa fa-list"),
- |_| Syslog::new().into(), // fixme: use JournalView instead?
+ |_| {
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
+ .with_child(Syslog::new())
+ .into() // fixme: use JournalView instead?
+ },
)
.with_item_builder(
TabBarItem::new()
.key("tasks")
.label("Tasks")
.icon_class("fa fa-list-alt"),
- |_| Tasks::new().into(),
+ |_| {
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
+ .with_child(Tasks::new())
+ .into()
+ },
);
NavigationContainer::new().with_child(panel).into()
diff --git a/ui/src/certificates.rs b/ui/src/certificates.rs
index 39929b4..d348785 100644
--- a/ui/src/certificates.rs
+++ b/ui/src/certificates.rs
@@ -7,6 +7,8 @@ use proxmox_yew_comp::acme::{
AcmeAccountsPanel, AcmeDomainsPanel, AcmePluginsPanel, CertificateList,
};
+use crate::widget::ContentSpacer;
+
#[function_component(CertificatesPanel)]
pub fn certificates_panel() -> Html {
let panel = TabPanel::new()
@@ -19,23 +21,43 @@ pub fn certificates_panel() -> Html {
TabBarItem::new()
.key("certificate_List")
.label("Certificates"),
- |_| CertificateList::new().into(),
+ |_| {
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
+ .with_child(CertificateList::new())
+ .into()
+ },
)
.with_item_builder(
TabBarItem::new().key("acme_domains").label("ACME Domains"),
- |_| AcmeDomainsPanel::new().url("/config/certificate").into(),
+ |_| {
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
+ .with_child(AcmeDomainsPanel::new().url("/config/certificate"))
+ .into()
+ },
)
.with_item_builder(
TabBarItem::new()
.key("acme_accounts")
.label("ACME Accounts"),
- |_| AcmeAccountsPanel::new().into(),
+ |_| {
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
+ .with_child(AcmeAccountsPanel::new())
+ .into()
+ },
)
.with_item_builder(
TabBarItem::new()
.key("acme_plugins")
.label("Challenge Plugins"),
- |_| AcmePluginsPanel::new().into(),
+ |_| {
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
+ .with_child(AcmePluginsPanel::new())
+ .into()
+ },
);
NavigationContainer::new().with_child(panel).into()
diff --git a/ui/src/configuration/mod.rs b/ui/src/configuration/mod.rs
index 15421a4..63e741e 100644
--- a/ui/src/configuration/mod.rs
+++ b/ui/src/configuration/mod.rs
@@ -1,7 +1,7 @@
use pwt::prelude::*;
use pwt::props::StorageLocation;
use pwt::state::NavigationContainer;
-use pwt::widget::{Column, MiniScrollMode, Panel, TabBarItem, TabPanel};
+use pwt::widget::{Column, Container, MiniScrollMode, Panel, TabBarItem, TabPanel};
use proxmox_yew_comp::configuration::TimePanel;
use proxmox_yew_comp::configuration::{DnsPanel, NetworkView};
@@ -11,6 +11,8 @@ use proxmox_yew_comp::UserPanel;
mod other;
pub use other::OtherPanel;
+use crate::widget::ContentSpacer;
+
#[function_component(SystemConfiguration)]
pub fn system_configuration() -> Html {
let panel = TabPanel::new()
@@ -50,14 +52,24 @@ pub fn access_control() -> Html {
.key("user-management")
.icon_class("fa fa-user")
.label(tr!("User Management")),
- |_| UserPanel::new().into(),
+ |_| {
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
+ .with_child(UserPanel::new())
+ .into()
+ },
)
.with_item_builder(
TabBarItem::new()
.key("two-factor")
.icon_class("fa fa-key")
.label(tr!("Two Factor Authentication")),
- |_| TfaView::new().into(),
+ |_| {
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
+ .with_child(TfaView::new())
+ .into()
+ },
);
NavigationContainer::new().with_child(panel).into()
@@ -65,19 +77,15 @@ pub fn access_control() -> Html {
#[function_component(NetworkTimePanel)]
pub fn create_network_time_panel() -> Html {
- Column::new()
+ ContentSpacer::new()
.class("pwt-flex-fit")
- .padding(2)
- .gap(4)
.with_child(
Panel::new()
- .border(true)
.title(tr!("Time"))
.with_child(html! { <TimePanel/> }),
)
.with_child(
Panel::new()
- .border(true)
.title(tr!("DNS"))
.with_child(html! { <DnsPanel/> }),
)
@@ -85,7 +93,6 @@ pub fn create_network_time_panel() -> Html {
Panel::new()
.min_height(200)
.class("pwt-flex-fit")
- .border(true)
.title(tr!("Network Interfaces"))
.with_child(NetworkView::new()),
)
diff --git a/ui/src/configuration/other/mod.rs b/ui/src/configuration/other/mod.rs
index f2435ac..cdd8e98 100644
--- a/ui/src/configuration/other/mod.rs
+++ b/ui/src/configuration/other/mod.rs
@@ -1,14 +1,13 @@
use pwt::prelude::*;
-use pwt::widget::Column;
+
+use crate::widget::ContentSpacer;
mod webauthn;
#[function_component(OtherPanel)]
pub fn create_other_panel() -> Html {
- Column::new()
- .class("pwt-flex-fill")
- .padding(2)
- .gap(4)
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
.with_child(html! { <webauthn::WebauthnPanel/> })
.into()
}
diff --git a/ui/src/configuration/other/webauthn.rs b/ui/src/configuration/other/webauthn.rs
index 08bc09a..9748e3c 100644
--- a/ui/src/configuration/other/webauthn.rs
+++ b/ui/src/configuration/other/webauthn.rs
@@ -19,7 +19,6 @@ use proxmox_yew_comp::{ObjectGrid, ObjectGridRow};
#[function_component(WebauthnPanel)]
pub fn webauthn_panel() -> Html {
Panel::new()
- .border(true)
.title(tr!("WebAuthn TFA"))
.with_child(object_grid())
.into()
diff --git a/ui/src/dashboard/mod.rs b/ui/src/dashboard/mod.rs
index fe7ac1b..49239b2 100644
--- a/ui/src/dashboard/mod.rs
+++ b/ui/src/dashboard/mod.rs
@@ -18,7 +18,7 @@ use pwt::{
use pdm_api_types::resource::{GuestStatusCount, NodeStatusCount, ResourcesStatus};
use pdm_client::types::TopEntity;
-use crate::{remotes::AddWizard, RemoteList};
+use crate::{remotes::AddWizard, widget::ContentSpacer, RemoteList};
mod top_entities;
pub use top_entities::TopEntities;
@@ -275,17 +275,15 @@ impl Component for PdmDashboard {
let content = Column::new()
.class(FlexFit)
- .padding(4)
- .gap(4)
.with_child(
- Row::new()
- .gap(4)
+ ContentSpacer::new()
+ .class("pwt-flex-direction-row")
.class(FlexWrap::Wrap)
.with_child(
Panel::new()
.title(self.create_title_with_icon("server", tr!("Remotes")))
.flex(1.0)
- .border(true)
+ //.border(true)
.width(300)
.min_height(175)
.with_tool(
@@ -389,10 +387,11 @@ impl Component for PdmDashboard {
.with_child(SubscriptionInfo::new()),
)
.with_child(
- Row::new()
- .gap(4)
+ ContentSpacer::new()
+ .class("pwt-flex-direction-row")
+ .style("padding-top", "0")
.class(FlexWrap::Wrap)
- .min_height(175)
+ //.min_height(175)
.with_child(self.create_top_entities_panel(
"desktop",
tr!("Guests With the Highest CPU Usage"),
diff --git a/ui/src/main_menu.rs b/ui/src/main_menu.rs
index e75eb90..3d2b5a0 100644
--- a/ui/src/main_menu.rs
+++ b/ui/src/main_menu.rs
@@ -16,6 +16,7 @@ use proxmox_yew_comp::{NotesView, XTermJs};
use pdm_api_types::remotes::RemoteType;
+use crate::widget::ContentSpacer;
use crate::{
AccessControl, CertificatesPanel, Dashboard, RemoteConfigPanel, RemoteList,
ServerAdministration, SystemConfiguration,
@@ -177,14 +178,14 @@ impl Component for PdmMainMenu {
"notes",
Some("fa fa-sticky-note-o"),
move |_| {
- NotesView::new("/config/notes")
- .on_submit(|notes| async move {
- proxmox_yew_comp::http_put(
- "/config/notes",
- Some(serde_json::to_value(¬es)?),
- )
+ let notes = NotesView::new("/config/notes").on_submit(|notes| async move {
+ proxmox_yew_comp::http_put("/config/notes", Some(serde_json::to_value(¬es)?))
.await
- })
+ });
+
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
+ .with_child(notes)
.into()
},
);
@@ -274,7 +275,12 @@ impl Component for PdmMainMenu {
} else {
"fa fa-server"
}),
- |_| RemoteConfigPanel::new().into(),
+ |_| {
+ ContentSpacer::new()
+ .class("pwt-flex-fit")
+ .with_child(RemoteConfigPanel::new())
+ .into()
+ },
remote_submenu,
);
diff --git a/ui/src/pve/mod.rs b/ui/src/pve/mod.rs
index 7925bbf..bde89b5 100644
--- a/ui/src/pve/mod.rs
+++ b/ui/src/pve/mod.rs
@@ -31,7 +31,7 @@ pub mod utils;
mod tree;
use tree::PveTreeNode;
-use crate::get_deep_url;
+use crate::{get_deep_url, widget::ContentSpacer};
#[derive(Debug, Eq, PartialEq, Properties)]
pub struct PveRemote {
@@ -163,10 +163,9 @@ impl LoadableComponent for PveRemoteComp {
.with_child(tr! {"Remote '{0}'", ctx.props().remote})
.into();
- let content = Row::new()
+ let content = ContentSpacer::new()
.class(FlexFit)
- .padding(4)
- .gap(4)
+ .class("pwt-flex-direction-row")
.with_child(
Panel::new()
.min_width(500)
diff --git a/ui/src/widget/content_spacer.rs b/ui/src/widget/content_spacer.rs
new file mode 100644
index 0000000..575e332
--- /dev/null
+++ b/ui/src/widget/content_spacer.rs
@@ -0,0 +1,99 @@
+use std::rc::Rc;
+
+use pwt::props::{AsClassesMut, AsCssStylesMut, CssStyles};
+use pwt::widget::Container;
+use yew::prelude::*;
+use yew::virtual_dom::{Key, VComp, VNode};
+
+use pwt::prelude::*;
+
+#[derive(Clone, PartialEq, Properties)]
+pub struct ContentSpacer {
+ /// The yew component key.
+ #[prop_or_default]
+ pub key: Option<Key>,
+
+ #[prop_or_default]
+ pub children: Vec<VNode>,
+
+ /// CSS class of the container.
+ #[prop_or_default]
+ pub class: Classes,
+
+ /// CSS style for the dialog window
+ #[prop_or_default]
+ pub styles: CssStyles,
+}
+
+impl ContentSpacer {
+ pub fn new() -> Self {
+ yew::props!(Self {})
+ }
+
+ /// Builder style method to set the yew `key` property.
+ pub fn key(mut self, key: impl IntoOptionalKey) -> Self {
+ self.key = key.into_optional_key();
+ self
+ }
+
+ /// Builder style method to add a html class.
+ pub fn class(mut self, class: impl Into<Classes>) -> Self {
+ self.add_class(class);
+ self
+ }
+
+ /// Method to add a html class.
+ pub fn add_class(&mut self, class: impl Into<Classes>) {
+ self.class.push(class);
+ }
+}
+
+impl ContainerBuilder for ContentSpacer {
+ fn as_children_mut(&mut self) -> &mut Vec<VNode> {
+ &mut self.children
+ }
+}
+
+impl AsClassesMut for ContentSpacer {
+ fn as_classes_mut(&mut self) -> &mut yew::Classes {
+ &mut self.class
+ }
+}
+
+impl AsCssStylesMut for ContentSpacer {
+ fn as_css_styles_mut(&mut self) -> &mut CssStyles {
+ &mut self.styles
+ }
+}
+
+impl WidgetStyleBuilder for ContentSpacer {}
+
+pub struct ProxmoxContentSpacer {}
+
+impl Component for ProxmoxContentSpacer {
+ type Message = ();
+ type Properties = ContentSpacer;
+
+ fn create(_ctx: &Context<Self>) -> Self {
+ Self {}
+ }
+
+ fn view(&self, ctx: &Context<Self>) -> Html {
+ let props = ctx.props();
+ Container::new()
+ .class("proxmox-content-spacer")
+ .class((props.children.len() < 2).then(|| "proxmox-content-spacer-with-one-child"))
+ .class(props.class.clone())
+ .styles(props.styles.clone())
+ .children(props.children.clone())
+ .into()
+ }
+}
+
+impl From<ContentSpacer> for VNode {
+ fn from(val: ContentSpacer) -> Self {
+ let key = val.key.clone();
+ let comp = VComp::new::<ProxmoxContentSpacer>(Rc::new(val), key);
+ VNode::from(comp)
+ }
+}
diff --git a/ui/src/widget/mod.rs b/ui/src/widget/mod.rs
index b885d1b..1104b01 100644
--- a/ui/src/widget/mod.rs
+++ b/ui/src/widget/mod.rs
@@ -1,3 +1,6 @@
+mod content_spacer;
+pub use content_spacer::ContentSpacer;
+
mod migrate_window;
pub use migrate_window::MigrateWindow;
--
2.39.5
More information about the pdm-devel
mailing list