Skip to content

Commit 991f38b

Browse files
committed
Fix #77: add button to switch between light and dark mode
In order to do so, a few things have changed: caching is gone, style.css is not a template anymore, a cookie value is extracted on each request and a new `/theme` change route has been added.
1 parent 3330f6f commit 991f38b

File tree

21 files changed

+261
-227
lines changed

21 files changed

+261
-227
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
- `WASTEBIN_THEME` configuration key to change the default theme from `ayu` to
88
`base16ocean`, `coldark`, `gruvbox`, `monokai`, `onehalf` or `solarized`.
9+
- UI button to switch between light and dark mode and cookie to store the
10+
preference. By default the system setting is used.
911

1012
### Changed
1113

Cargo.lock

Lines changed: 0 additions & 120 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ askama_axum = { version = "0.4" }
1010
async-compression = { version = "0.4", features = ["tokio", "zstd"] }
1111
axum = { version = "0.7", features = ["json", "query"] }
1212
axum-extra = { version = "0.9", features = ["cookie-signed", "typed-header"] }
13-
axum-response-cache = { version = "0.2.0", default-features = false, features = ["axum07"] }
1413
bytes = "1"
1514
cached = { version = "0.54.0", default-features = false }
1615
chacha20poly1305 = "0.10.1"

src/assets.rs

Lines changed: 25 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use askama::Template;
21
use axum::response::{IntoResponse, Response};
32
use axum_extra::{headers, TypedHeader};
43
use sha2::{Digest, Sha256};
54
use std::io::Cursor;
65
use std::time::Duration;
7-
use syntect::highlighting::{Color, ThemeSet};
6+
use syntect::highlighting::{self, ThemeSet};
87
use syntect::html::{css_for_theme_with_class_style, ClassStyle};
98
use two_face::theme::EmbeddedThemeName;
109

@@ -87,59 +86,35 @@ pub struct Css {
8786
pub dark: Asset,
8887
}
8988

89+
/// Generate the highlighting colors for `theme` and add main foreground and background colors
90+
/// based on the theme.
91+
fn combined_css(theme: &highlighting::Theme) -> Vec<u8> {
92+
let fg = theme.settings.foreground.expect("existing color");
93+
let bg = theme.settings.background.expect("existing color");
94+
95+
let main_colors = format!(
96+
":root {{
97+
--main-bg-color: rgb({}, {}, {}, {});
98+
--main-fg-color: rgb({}, {}, {}, {});
99+
}}",
100+
bg.r, bg.g, bg.b, bg.a, fg.r, fg.g, fg.b, fg.a
101+
);
102+
103+
format!(
104+
"{main_colors} {}",
105+
css_for_theme_with_class_style(theme, ClassStyle::Spaced).expect("generating CSS")
106+
)
107+
.into_bytes()
108+
}
109+
90110
impl Css {
91111
/// Create CSS assets for `theme`.
92112
pub fn new(theme: Theme) -> Self {
93-
#[derive(Template)]
94-
#[template(path = "style.css", escape = "none")]
95-
struct StyleCss {
96-
light_background: Color,
97-
light_foreground: Color,
98-
dark_background: Color,
99-
dark_foreground: Color,
100-
light_asset: Asset,
101-
dark_asset: Asset,
102-
}
103-
104113
let light_theme = light_theme(theme);
105114
let dark_theme = dark_theme(theme);
106-
107-
// SAFETY: all supported color themes have a defined foreground and background color.
108-
let light_foreground = light_theme.settings.foreground.expect("existing color");
109-
let light_background = light_theme.settings.background.expect("existing color");
110-
let dark_foreground = dark_theme.settings.foreground.expect("existing color");
111-
let dark_background = dark_theme.settings.background.expect("existing color");
112-
113-
let light = Asset::new_hashed(
114-
"light",
115-
Kind::Css,
116-
css_for_theme_with_class_style(&light_theme, ClassStyle::Spaced)
117-
.expect("generating CSS")
118-
.into_bytes(),
119-
);
120-
121-
let dark = Asset::new_hashed(
122-
"dark",
123-
Kind::Css,
124-
css_for_theme_with_class_style(&dark_theme, ClassStyle::Spaced)
125-
.expect("generating CSS")
126-
.into_bytes(),
127-
);
128-
129-
let style = StyleCss {
130-
light_background,
131-
light_foreground,
132-
dark_background,
133-
dark_foreground,
134-
light_asset: light.clone(),
135-
dark_asset: dark.clone(),
136-
};
137-
138-
let style = Asset::new_hashed(
139-
"style",
140-
Kind::Css,
141-
style.render().expect("rendering style css").into_bytes(),
142-
);
115+
let style = Asset::new_hashed("style", Kind::Css, include_str!("style.css").into());
116+
let light = Asset::new_hashed("light", Kind::Css, combined_css(&light_theme));
117+
let dark = Asset::new_hashed("dark", Kind::Css, combined_css(&dark_theme));
143118

144119
Self { style, light, dark }
145120
}

src/handlers/delete.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::handlers::extract::Theme;
12
use crate::handlers::html::{make_error, ErrorResponse};
23
use crate::{Database, Error, Page};
34
use axum::extract::{Path, State};
@@ -8,6 +9,7 @@ pub async fn get(
89
Path(id): Path<String>,
910
State(db): State<Database>,
1011
State(page): State<Page>,
12+
theme: Option<Theme>,
1113
jar: SignedCookieJar,
1214
) -> Result<Redirect, ErrorResponse> {
1315
async {
@@ -30,7 +32,7 @@ pub async fn get(
3032
Ok(Redirect::to("/"))
3133
}
3234
.await
33-
.map_err(|err| make_error(err, page.clone()))
35+
.map_err(|err| make_error(err, page.clone(), theme))
3436
}
3537

3638
#[cfg(test)]

src/handlers/download.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::cache::Key;
22
use crate::db::read::Entry;
3-
use crate::handlers::extract::Password;
3+
use crate::handlers::extract::{Password, Theme};
44
use crate::handlers::html::{make_error, ErrorResponse, PasswordInput};
55
use crate::{Database, Error, Page};
66
use axum::extract::{Path, State};
@@ -13,6 +13,7 @@ pub async fn get(
1313
Path(id): Path<String>,
1414
State(db): State<Database>,
1515
State(page): State<Page>,
16+
theme: Option<Theme>,
1617
password: Option<Password>,
1718
) -> Result<Response, ErrorResponse> {
1819
async {
@@ -26,14 +27,15 @@ pub async fn get(
2627
Ok(Entry::Expired) => Err(Error::NotFound),
2728
Err(Error::NoPassword) => Ok(PasswordInput {
2829
page: page.clone(),
30+
theme: theme.clone(),
2931
id: key.id.to_string(),
3032
}
3133
.into_response()),
3234
Err(err) => Err(err),
3335
}
3436
}
3537
.await
36-
.map_err(|err| make_error(err, page))
38+
.map_err(|err| make_error(err, page, theme))
3739
}
3840

3941
fn get_download(text: String, id: &str, extension: &str) -> impl IntoResponse {

0 commit comments

Comments
 (0)