[pdm-devel] [RFC: pdm] ui: make layout more consistent using new ContentSpacer widget
Dietmar Maurer
dietmar at proxmox.com
Fri Jan 10 12:28:44 CET 2025
Signed-off-by: Dietmar Maurer <dietmar at proxmox.com>
---
This only change the "Desktop" Theme layout.
TODO: change borders to shadows...
ui/css/crisp-yew-style.scss | 12 ++++
ui/css/desktop-yew-style.scss | 5 ++
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/main_menu.rs | 22 +++---
ui/src/widget/content_spacer.rs | 97 ++++++++++++++++++++++++++
ui/src/widget/mod.rs | 3 +
11 files changed, 223 insertions(+), 35 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..a14a277 100644
--- a/ui/css/desktop-yew-style.scss
+++ b/ui/css/desktop-yew-style.scss
@@ -1,2 +1,7 @@
@import "../pwt-assets/scss/desktop-yew-style";
@import "pdm";
+
+.proxmox-content-spacer {
+ padding: var(--pwt-spacer-4);
+ gap: var(--pwt-spacer-4);
+}
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/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/widget/content_spacer.rs b/ui/src/widget/content_spacer.rs
new file mode 100644
index 0000000..37f7e93
--- /dev/null
+++ b/ui/src/widget/content_spacer.rs
@@ -0,0 +1,97 @@
+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
+ }
+}
+
+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