Skip to content

Commit 07290d1

Browse files
Dan notification banner (#1197)
1 parent 73f24dd commit 07290d1

File tree

16 files changed

+298
-13
lines changed

16 files changed

+298
-13
lines changed

pgml-dashboard/src/api/cms.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ impl Collection {
266266
Some(cluster.context.user.clone())
267267
};
268268

269-
let mut layout = crate::templates::Layout::new(&title);
269+
let mut layout = crate::templates::Layout::new(&title, Some(cluster.clone()));
270270
if let Some(image) = image {
271271
// translate relative url into absolute for head social sharing
272272
let parts = image.split(".gitbook/assets/").collect::<Vec<&str>>();

pgml-dashboard/src/components/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ pub use nav_link::NavLink;
5353
// src/components/navigation
5454
pub mod navigation;
5555

56+
// src/components/notifications
57+
pub mod notifications;
58+
5659
// src/components/postgres_logo
5760
pub mod postgres_logo;
5861
pub use postgres_logo::PostgresLogo;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#notifications-banner {
2+
margin-left: calc(var(--bs-gutter-x) * -0.5);
3+
margin-right: calc(var(--bs-gutter-x) * -0.5);
4+
}
5+
6+
div[data-controller="notifications-banner"] {
7+
.btn-tertiary {
8+
border: 0px;
9+
}
10+
.news {
11+
background-color: #{$gray-100};
12+
color: #{$gray-900};
13+
.btn-tertiary:hover {
14+
filter: brightness(0.9);
15+
}
16+
}
17+
.blog {
18+
background-color: #{$neon-shade-100};
19+
.btn-tertiary {
20+
filter: brightness(1.5);
21+
}
22+
}
23+
.launch {
24+
background-color: #{$magenta-shade-200};
25+
.btn-tertiary {
26+
filter: brightness(1.5);
27+
}
28+
}
29+
.tip {
30+
background-color: #{$gray-900};
31+
}
32+
.level1 {
33+
background-color: #FFFF00;
34+
color: #{$gray-900};
35+
}
36+
.level2 {
37+
background-color: #FF6929;
38+
color: #{$gray-900};
39+
}
40+
.level3 {
41+
background-color: #{$peach-shade-200};
42+
}
43+
44+
.close-dark {
45+
color: #{$gray-900};
46+
}
47+
.close-light {
48+
color: #{$gray-100};
49+
}
50+
.close-dark, .close-light {
51+
margin-left: -100%;
52+
}
53+
54+
.message-area {
55+
max-width: 75vw;
56+
}
57+
58+
.banner {
59+
min-height: 2rem;
60+
}
61+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Controller } from '@hotwired/stimulus'
2+
3+
export default class extends Controller {}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use crate::{Notification, NotificationLevel};
2+
use pgml_components::component;
3+
use sailfish::TemplateOnce;
4+
5+
#[derive(TemplateOnce, Default, Clone)]
6+
#[template(path = "notifications/banner/template.html")]
7+
pub struct Banner {
8+
pub notification: Notification,
9+
pub remove_banner: bool,
10+
}
11+
12+
impl Banner {
13+
pub fn new() -> Banner {
14+
Banner {
15+
notification: Notification::default(),
16+
remove_banner: false,
17+
}
18+
}
19+
20+
pub fn from_notification(notification: Notification) -> Banner {
21+
Banner {
22+
notification,
23+
remove_banner: false,
24+
}
25+
}
26+
27+
pub fn remove_banner(mut self, remove_banner: bool) -> Banner {
28+
self.remove_banner = remove_banner;
29+
self
30+
}
31+
}
32+
33+
component!(Banner);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<% use crate::NotificationLevel; %>
2+
<turbo-frame id="notifications-banner" class="position-relative d-block">
3+
<% if !remove_banner {%>
4+
<div data-controller="notifications-banner">
5+
<div class="<%- notification.level.to_string() %> W-100">
6+
<div class="banner d-flex container p-1">
7+
<div class="flex-grow-1 d-flex flex-column flex-md-row justify-content-center align-items-center row-gap-0 column-gap-3 fw-semibold overflow-hidden">
8+
<div class="mx-3 overflow-hidden" style="max-width: 80%;">
9+
<p class="m-0 text-center"><%- notification.message %></p>
10+
</div>
11+
<% if notification.link.is_some() {%>
12+
<a class="btn btn-tertiary fw-semibold p-0" href="<%- notification.link.unwrap() %>" data-turbo="false">
13+
Learn More
14+
<span class="material-symbols-outlined">arrow_forward</span>
15+
</a>
16+
<% } %>
17+
</div>
18+
<% if notification.dismissible {%>
19+
<a class="w-0 overflow-visible d-flex align-items-center" style="right: 4vw" href="/dashboard/notifications/remove_banner?id=<%- notification.id%>">
20+
<span class="material-symbols-outlined <% if notification.level == NotificationLevel::Tip {%>close-light<% } else {%>close-dark<% } %>">
21+
close
22+
</span></a>
23+
<% } %>
24+
</div>
25+
</div>
26+
</div>
27+
<% } %>
28+
</turbo-frame>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// This file is automatically generated.
2+
// You shouldn't modify it manually.
3+
4+
// src/components/notifications/banner
5+
pub mod banner;
6+
pub use banner::Banner;

pgml-dashboard/src/guards.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ use sqlx::{postgres::PgPoolOptions, Executor, PgPool};
88

99
static POOL: OnceCell<PgPool> = OnceCell::new();
1010

11-
use crate::{models, utils::config, Context};
11+
use crate::{models, utils::config, Context, Notification};
1212

13-
#[derive(Debug)]
13+
#[derive(Debug, Clone, Default)]
1414
pub struct Cluster {
1515
pub pool: Option<PgPool>,
1616
pub context: Context,
17+
pub notifications: Option<Vec<Notification>>,
1718
}
1819

1920
impl Cluster {
@@ -132,6 +133,7 @@ impl Cluster {
132133
lower_left_nav: StaticNav::default(),
133134
marketing_footer: MarketingFooter::new().render_once().unwrap(),
134135
},
136+
notifications: None,
135137
}
136138
}
137139
}

pgml-dashboard/src/lib.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
extern crate rocket;
33

44
use rocket::form::Form;
5+
use rocket::http::{Cookie, CookieJar};
56
use rocket::response::Redirect;
67
use rocket::route::Route;
78
use rocket::serde::json::Json;
@@ -20,6 +21,7 @@ pub mod templates;
2021
pub mod types;
2122
pub mod utils;
2223

24+
use components::notifications::banner::Banner;
2325
use guards::{Cluster, ConnectedCluster};
2426
use responses::{BadRequest, Error, ResponseOk};
2527
use templates::{
@@ -28,6 +30,10 @@ use templates::{
2830
};
2931
use utils::tabs;
3032

33+
use crate::utils::cookies::Notifications;
34+
use std::collections::hash_map::DefaultHasher;
35+
use std::hash::{Hash, Hasher};
36+
3137
#[derive(Debug, Default, Clone)]
3238
pub struct ClustersSettings {
3339
pub max_connections: u32,
@@ -50,6 +56,77 @@ pub struct Context {
5056
pub marketing_footer: String,
5157
}
5258

59+
#[derive(Debug, Clone, Default)]
60+
pub struct Notification {
61+
pub message: String,
62+
pub level: NotificationLevel,
63+
pub id: String,
64+
pub dismissible: bool,
65+
pub viewed: bool,
66+
pub link: Option<String>,
67+
}
68+
impl Notification {
69+
pub fn new(message: &str) -> Notification {
70+
let mut s = DefaultHasher::new();
71+
message.hash(&mut s);
72+
73+
Notification {
74+
message: message.to_string(),
75+
level: NotificationLevel::News,
76+
id: s.finish().to_string(),
77+
dismissible: true,
78+
viewed: false,
79+
link: None,
80+
}
81+
}
82+
83+
pub fn level(mut self, level: &NotificationLevel) -> Notification {
84+
self.level = level.clone();
85+
self
86+
}
87+
88+
pub fn dismissible(mut self, dismissible: bool) -> Notification {
89+
self.dismissible = dismissible;
90+
self
91+
}
92+
93+
pub fn link(mut self, link: &str) -> Notification {
94+
self.link = Some(link.into());
95+
self
96+
}
97+
98+
pub fn viewed(mut self, viewed: bool) -> Notification {
99+
self.viewed = viewed;
100+
self
101+
}
102+
}
103+
104+
impl std::fmt::Display for NotificationLevel {
105+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106+
match self {
107+
NotificationLevel::News => write!(f, "news"),
108+
NotificationLevel::Blog => write!(f, "blog"),
109+
NotificationLevel::Launch => write!(f, "launch"),
110+
NotificationLevel::Tip => write!(f, "tip"),
111+
NotificationLevel::Level1 => write!(f, "level1"),
112+
NotificationLevel::Level2 => write!(f, "level2"),
113+
NotificationLevel::Level3 => write!(f, "level3"),
114+
}
115+
}
116+
}
117+
118+
#[derive(Debug, Clone, Default, PartialEq)]
119+
pub enum NotificationLevel {
120+
#[default]
121+
News,
122+
Blog,
123+
Launch,
124+
Tip,
125+
Level1,
126+
Level2,
127+
Level3,
128+
}
129+
53130
#[get("/projects")]
54131
pub async fn project_index(cluster: ConnectedCluster<'_>) -> Result<ResponseOk, Error> {
55132
Ok(ResponseOk(
@@ -672,6 +749,30 @@ pub async fn playground(cluster: &Cluster) -> Result<ResponseOk, Error> {
672749
Ok(ResponseOk(layout.render(templates::Playground {})))
673750
}
674751

752+
#[get("/notifications/remove_banner?<id>")]
753+
pub fn remove_banner(id: String, cookies: &CookieJar<'_>, context: &Cluster) -> ResponseOk {
754+
let mut viewed = Notifications::get_viewed(cookies);
755+
756+
viewed.push(id);
757+
Notifications::update_viewed(&viewed, cookies);
758+
759+
match context.notifications.as_ref() {
760+
Some(notifications) => {
761+
for notification in notifications {
762+
if !viewed.contains(&notification.id) {
763+
return ResponseOk(
764+
Banner::from_notification(notification.clone())
765+
.render_once()
766+
.unwrap(),
767+
);
768+
}
769+
}
770+
return ResponseOk(Banner::new().remove_banner(true).render_once().unwrap());
771+
}
772+
None => return ResponseOk(Banner::new().remove_banner(true).render_once().unwrap()),
773+
}
774+
}
775+
675776
pub fn routes() -> Vec<Route> {
676777
routes![
677778
notebook_index,
@@ -699,6 +800,7 @@ pub fn routes() -> Vec<Route> {
699800
uploaded_index,
700801
dashboard,
701802
notebook_reorder,
803+
remove_banner,
702804
]
703805
}
704806

pgml-dashboard/src/responses.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,8 @@ impl<'r> response::Responder<'r, 'r> for Response {
8181
let body = match self.body {
8282
Some(body) => body,
8383
None => match self.status.code {
84-
404 => {
85-
templates::Layout::new("Internal Server Error").render(templates::NotFound {})
86-
}
84+
404 => templates::Layout::new("Internal Server Error", None)
85+
.render(templates::NotFound {}),
8786
_ => "".into(),
8887
},
8988
};
@@ -134,8 +133,8 @@ impl<'r> response::Responder<'r, 'r> for Error {
134133
"".into()
135134
};
136135

137-
let body =
138-
templates::Layout::new("Internal Server Error").render(templates::Error { error });
136+
let body = templates::Layout::new("Internal Server Error", None)
137+
.render(templates::Error { error });
139138

140139
response::Response::build_from(body.respond_to(request)?)
141140
.header(ContentType::new("text", "html"))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy