Skip to content

Dan notification banner #1197

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pgml-dashboard/src/api/cms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ impl Collection {
Some(cluster.context.user.clone())
};

let mut layout = crate::templates::Layout::new(&title);
let mut layout = crate::templates::Layout::new(&title, Some(cluster.clone()));
if let Some(image) = image {
// translate relative url into absolute for head social sharing
let parts = image.split(".gitbook/assets/").collect::<Vec<&str>>();
Expand Down
3 changes: 3 additions & 0 deletions pgml-dashboard/src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ pub use nav_link::NavLink;
// src/components/navigation
pub mod navigation;

// src/components/notifications
pub mod notifications;

// src/components/postgres_logo
pub mod postgres_logo;
pub use postgres_logo::PostgresLogo;
Expand Down
61 changes: 61 additions & 0 deletions pgml-dashboard/src/components/notifications/banner/banner.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#notifications-banner {
margin-left: calc(var(--bs-gutter-x) * -0.5);
margin-right: calc(var(--bs-gutter-x) * -0.5);
}

div[data-controller="notifications-banner"] {
.btn-tertiary {
border: 0px;
}
.news {
background-color: #{$gray-100};
color: #{$gray-900};
.btn-tertiary:hover {
filter: brightness(0.9);
}
}
.blog {
background-color: #{$neon-shade-100};
.btn-tertiary {
filter: brightness(1.5);
}
}
.launch {
background-color: #{$magenta-shade-200};
.btn-tertiary {
filter: brightness(1.5);
}
}
.tip {
background-color: #{$gray-900};
}
.level1 {
background-color: #FFFF00;
color: #{$gray-900};
}
.level2 {
background-color: #FF6929;
color: #{$gray-900};
}
.level3 {
background-color: #{$peach-shade-200};
}

.close-dark {
color: #{$gray-900};
}
.close-light {
color: #{$gray-100};
}
.close-dark, .close-light {
margin-left: -100%;
}

.message-area {
max-width: 75vw;
}

.banner {
min-height: 2rem;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {}
33 changes: 33 additions & 0 deletions pgml-dashboard/src/components/notifications/banner/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use crate::{Notification, NotificationLevel};
use pgml_components::component;
use sailfish::TemplateOnce;

#[derive(TemplateOnce, Default, Clone)]
#[template(path = "notifications/banner/template.html")]
pub struct Banner {
pub notification: Notification,
pub remove_banner: bool,
}

impl Banner {
pub fn new() -> Banner {
Banner {
notification: Notification::default(),
remove_banner: false,
}
}

pub fn from_notification(notification: Notification) -> Banner {
Banner {
notification,
remove_banner: false,
}
}

pub fn remove_banner(mut self, remove_banner: bool) -> Banner {
self.remove_banner = remove_banner;
self
}
}

component!(Banner);
28 changes: 28 additions & 0 deletions pgml-dashboard/src/components/notifications/banner/template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<% use crate::NotificationLevel; %>
<turbo-frame id="notifications-banner" class="position-relative d-block">
<% if !remove_banner {%>
<div data-controller="notifications-banner">
<div class="<%- notification.level.to_string() %> W-100">
<div class="banner d-flex container p-1">
<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">
<div class="mx-3 overflow-hidden" style="max-width: 80%;">
<p class="m-0 text-center"><%- notification.message %></p>
</div>
<% if notification.link.is_some() {%>
<a class="btn btn-tertiary fw-semibold p-0" href="<%- notification.link.unwrap() %>" data-turbo="false">
Learn More
<span class="material-symbols-outlined">arrow_forward</span>
</a>
<% } %>
</div>
<% if notification.dismissible {%>
<a class="w-0 overflow-visible d-flex align-items-center" style="right: 4vw" href="/dashboard/notifications/remove_banner?id=<%- notification.id%>">
<span class="material-symbols-outlined <% if notification.level == NotificationLevel::Tip {%>close-light<% } else {%>close-dark<% } %>">
close
</span></a>
<% } %>
</div>
</div>
</div>
<% } %>
</turbo-frame>
6 changes: 6 additions & 0 deletions pgml-dashboard/src/components/notifications/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This file is automatically generated.
// You shouldn't modify it manually.

// src/components/notifications/banner
pub mod banner;
pub use banner::Banner;
6 changes: 4 additions & 2 deletions pgml-dashboard/src/guards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ use sqlx::{postgres::PgPoolOptions, Executor, PgPool};

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

use crate::{models, utils::config, Context};
use crate::{models, utils::config, Context, Notification};

#[derive(Debug)]
#[derive(Debug, Clone, Default)]
pub struct Cluster {
pub pool: Option<PgPool>,
pub context: Context,
pub notifications: Option<Vec<Notification>>,
}

impl Cluster {
Expand Down Expand Up @@ -132,6 +133,7 @@ impl Cluster {
lower_left_nav: StaticNav::default(),
marketing_footer: MarketingFooter::new().render_once().unwrap(),
},
notifications: None,
}
}
}
Expand Down
102 changes: 102 additions & 0 deletions pgml-dashboard/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
extern crate rocket;

use rocket::form::Form;
use rocket::http::{Cookie, CookieJar};
use rocket::response::Redirect;
use rocket::route::Route;
use rocket::serde::json::Json;
Expand All @@ -20,6 +21,7 @@ pub mod templates;
pub mod types;
pub mod utils;

use components::notifications::banner::Banner;
use guards::{Cluster, ConnectedCluster};
use responses::{BadRequest, Error, ResponseOk};
use templates::{
Expand All @@ -28,6 +30,10 @@ use templates::{
};
use utils::tabs;

use crate::utils::cookies::Notifications;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

#[derive(Debug, Default, Clone)]
pub struct ClustersSettings {
pub max_connections: u32,
Expand All @@ -50,6 +56,77 @@ pub struct Context {
pub marketing_footer: String,
}

#[derive(Debug, Clone, Default)]
pub struct Notification {
pub message: String,
pub level: NotificationLevel,
pub id: String,
pub dismissible: bool,
pub viewed: bool,
pub link: Option<String>,
}
impl Notification {
pub fn new(message: &str) -> Notification {
let mut s = DefaultHasher::new();
message.hash(&mut s);

Notification {
message: message.to_string(),
level: NotificationLevel::News,
id: s.finish().to_string(),
dismissible: true,
viewed: false,
link: None,
}
}

pub fn level(mut self, level: &NotificationLevel) -> Notification {
self.level = level.clone();
self
}

pub fn dismissible(mut self, dismissible: bool) -> Notification {
self.dismissible = dismissible;
self
}

pub fn link(mut self, link: &str) -> Notification {
self.link = Some(link.into());
self
}

pub fn viewed(mut self, viewed: bool) -> Notification {
self.viewed = viewed;
self
}
}

impl std::fmt::Display for NotificationLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NotificationLevel::News => write!(f, "news"),
NotificationLevel::Blog => write!(f, "blog"),
NotificationLevel::Launch => write!(f, "launch"),
NotificationLevel::Tip => write!(f, "tip"),
NotificationLevel::Level1 => write!(f, "level1"),
NotificationLevel::Level2 => write!(f, "level2"),
NotificationLevel::Level3 => write!(f, "level3"),
}
}
}

#[derive(Debug, Clone, Default, PartialEq)]
pub enum NotificationLevel {
#[default]
News,
Blog,
Launch,
Tip,
Level1,
Level2,
Level3,
}

#[get("/projects")]
pub async fn project_index(cluster: ConnectedCluster<'_>) -> Result<ResponseOk, Error> {
Ok(ResponseOk(
Expand Down Expand Up @@ -672,6 +749,30 @@ pub async fn playground(cluster: &Cluster) -> Result<ResponseOk, Error> {
Ok(ResponseOk(layout.render(templates::Playground {})))
}

#[get("/notifications/remove_banner?<id>")]
pub fn remove_banner(id: String, cookies: &CookieJar<'_>, context: &Cluster) -> ResponseOk {
let mut viewed = Notifications::get_viewed(cookies);

viewed.push(id);
Notifications::update_viewed(&viewed, cookies);

match context.notifications.as_ref() {
Some(notifications) => {
for notification in notifications {
if !viewed.contains(&notification.id) {
return ResponseOk(
Banner::from_notification(notification.clone())
.render_once()
.unwrap(),
);
}
}
return ResponseOk(Banner::new().remove_banner(true).render_once().unwrap());
}
None => return ResponseOk(Banner::new().remove_banner(true).render_once().unwrap()),
}
}

pub fn routes() -> Vec<Route> {
routes![
notebook_index,
Expand Down Expand Up @@ -699,6 +800,7 @@ pub fn routes() -> Vec<Route> {
uploaded_index,
dashboard,
notebook_reorder,
remove_banner,
]
}

Expand Down
9 changes: 4 additions & 5 deletions pgml-dashboard/src/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,8 @@ impl<'r> response::Responder<'r, 'r> for Response {
let body = match self.body {
Some(body) => body,
None => match self.status.code {
404 => {
templates::Layout::new("Internal Server Error").render(templates::NotFound {})
}
404 => templates::Layout::new("Internal Server Error", None)
.render(templates::NotFound {}),
_ => "".into(),
},
};
Expand Down Expand Up @@ -134,8 +133,8 @@ impl<'r> response::Responder<'r, 'r> for Error {
"".into()
};

let body =
templates::Layout::new("Internal Server Error").render(templates::Error { error });
let body = templates::Layout::new("Internal Server Error", None)
.render(templates::Error { error });

response::Response::build_from(body.respond_to(request)?)
.header(ContentType::new("text", "html"))
Expand Down
13 changes: 12 additions & 1 deletion pgml-dashboard/src/templates/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use pgml_components::Component;
use std::collections::HashMap;

pub use crate::components::{self, cms::index_link::IndexLink, NavLink, StaticNav, StaticNavLink};
use components::notifications::banner::Banner;

use sailfish::TemplateOnce;
use sqlx::postgres::types::PgMoney;
Expand Down Expand Up @@ -36,12 +37,22 @@ pub struct Layout {
pub nav_links: Vec<IndexLink>,
pub toc_links: Vec<docs::TocLink>,
pub footer: String,
pub banner: Option<Banner>,
}

impl Layout {
pub fn new(title: &str) -> Self {
pub fn new(title: &str, context: Option<crate::guards::Cluster>) -> Self {
let banner = match context.as_ref() {
Some(context) => match &context.notifications {
Some(notification) => Some(Banner::from_notification(notification[0].clone())),
None => None,
},
None => None,
};

Layout {
head: Head::new().title(title),
banner,
..Default::default()
}
}
Expand Down
Loading