all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Dietmar Maurer <dietmar@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [pdm-devel] [RFC pdm v2] ui: make layout more consistent using new ContentSpacer widget
Date: Fri, 10 Jan 2025 13:50:23 +0100	[thread overview]
Message-ID: <20250110125023.190875-1-dietmar@proxmox.com> (raw)

Signed-off-by: Dietmar Maurer <dietmar@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(&notes)?),
-                        )
+                let notes = NotesView::new("/config/notes").on_submit(|notes| async move {
+                    proxmox_yew_comp::http_put("/config/notes", Some(serde_json::to_value(&notes)?))
                         .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


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


             reply	other threads:[~2025-01-10 12:51 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-01-10 12:50 Dietmar Maurer [this message]
2025-01-15 15:48 ` Thomas Lamprecht
2025-01-15 17:38   ` Dietmar Maurer

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=20250110125023.190875-1-dietmar@proxmox.com \
    --to=dietmar@proxmox.com \
    --cc=pdm-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.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal