diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/args.rs | 5 | ||||
-rw-r--r-- | src/listing.rs | 23 | ||||
-rw-r--r-- | src/main.rs | 5 | ||||
-rw-r--r-- | src/renderer.rs | 135 |
4 files changed, 111 insertions, 57 deletions
diff --git a/src/args.rs b/src/args.rs index a5bcfea..bf16291 100644 --- a/src/args.rs +++ b/src/args.rs @@ -77,6 +77,10 @@ struct CLIArgs { )] color_scheme: themes::ColorScheme, + /// Enable QR code display + #[structopt(short = "q", long = "qrcode")] + qrcode: bool, + /// Enable file uploading #[structopt(short = "u", long = "upload-files")] file_upload: bool, @@ -188,6 +192,7 @@ pub fn parse_args() -> crate::MiniserveConfig { default_color_scheme, index: args.index, overwrite_files: args.overwrite_files, + show_qrcode: args.qrcode, file_upload: args.file_upload, tar_enabled: args.enable_tar, zip_enabled: args.enable_zip, diff --git a/src/listing.rs b/src/listing.rs index 9ba596a..6181c3a 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -9,6 +9,7 @@ use std::io; use std::path::{Path, PathBuf}; use std::time::SystemTime; use strum_macros::{Display, EnumString}; +use qrcodegen::{QrCode, QrCodeEcc}; use crate::archive::CompressionMethod; use crate::errors::{self, ContextualError}; @@ -24,6 +25,7 @@ pub struct QueryParameters { pub sort: Option<SortingMethod>, pub order: Option<SortingOrder>, pub theme: Option<ColorScheme>, + qrcode: Option<String>, download: Option<CompressionMethod>, } @@ -135,6 +137,7 @@ pub fn directory_listing<S>( file_upload: bool, random_route: Option<String>, default_color_scheme: ColorScheme, + show_qrcode: bool, upload_route: String, tar_enabled: bool, zip_enabled: bool, @@ -163,6 +166,23 @@ pub fn directory_listing<S>( let query_params = extract_query_parameters(req); + // If the `qrcode` parameter is included in the url, then should respond to the QR code + if let Some(url) = query_params.qrcode { + let res = match QrCode::encode_text(&url, QrCodeEcc::Medium) { + Ok(qr) => { + HttpResponse::Ok() + .header("Content-Type", "image/svg+xml") + .body(qr.to_svg_string(2)) + }, + Err(err) => { + log::error!("URL is too long: {:?}", err); + HttpResponse::UriTooLong() + .body(Body::Empty) + } + }; + return Ok(res) + } + let mut entries: Vec<Entry> = Vec::new(); for entry in dir.path.read_dir()? { @@ -330,6 +350,7 @@ pub fn directory_listing<S>( query_params.order, default_color_scheme, color_scheme, + show_qrcode, file_upload, &upload_route, ¤t_dir.display().to_string(), @@ -348,6 +369,7 @@ pub fn extract_query_parameters<S>(req: &HttpRequest<S>) -> QueryParameters { order: query.order, download: query.download, theme: query.theme, + qrcode: query.qrcode.to_owned(), path: query.path.clone(), }, Err(e) => { @@ -358,6 +380,7 @@ pub fn extract_query_parameters<S>(req: &HttpRequest<S>) -> QueryParameters { order: None, download: None, theme: None, + qrcode: None, path: None, } } diff --git a/src/main.rs b/src/main.rs index 3ff35c8..a9e7944 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,6 +57,9 @@ pub struct MiniserveConfig { /// However, if a directory contains this file, miniserve will serve that file instead. pub index: Option<std::path::PathBuf>, + /// Enable QR code display + pub show_qrcode: bool, + /// Enable file upload pub file_upload: bool, @@ -258,6 +261,7 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> { let no_symlinks = app.state().no_symlinks; let random_route = app.state().random_route.clone(); let default_color_scheme = app.state().default_color_scheme; + let show_qrcode = app.state().show_qrcode; let file_upload = app.state().file_upload; let tar_enabled = app.state().tar_enabled; let zip_enabled = app.state().zip_enabled; @@ -288,6 +292,7 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> { file_upload, random_route.clone(), default_color_scheme, + show_qrcode, u_r.clone(), tar_enabled, zip_enabled, diff --git a/src/renderer.rs b/src/renderer.rs index accb49b..7270e8f 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -19,6 +19,7 @@ pub fn page( sort_order: Option<SortingOrder>, default_color_scheme: ColorScheme, color_scheme: ColorScheme, + show_qrcode: bool, file_upload: bool, upload_route: &str, current_dir: &str, @@ -46,7 +47,7 @@ pub fn page( } } } - (color_scheme_selector(sort_method, sort_order, color_scheme, default_color_scheme, serve_path)) + (color_scheme_selector(sort_method, sort_order, color_scheme, default_color_scheme, serve_path, show_qrcode)) div.container { span#top { } h1.title { "Index of " (serve_path) } @@ -133,24 +134,33 @@ fn color_scheme_selector( active_color_scheme: ColorScheme, default_color_scheme: ColorScheme, serve_path: &str, + show_qrcode: bool, ) -> Markup { html! { nav { - ul { - li { - a.change-theme href="#" title="Change theme" { - "Change theme..." + @if show_qrcode { + div { + p onmouseover="document.querySelector('#qrcode').src = `/?qrcode=${encodeURIComponent(window.location.href)}`" { + "QR code" } - ul { - @for color_scheme in ColorScheme::iter() { - @if active_color_scheme == color_scheme { - li.active { - (color_scheme_link(sort_method, sort_order, color_scheme, default_color_scheme, serve_path)) - } - } @else { - li { - (color_scheme_link(sort_method, sort_order, color_scheme, default_color_scheme, serve_path)) - } + div.qrcode { + img#qrcode alt="QR code" title="QR code of this page"; + } + } + } + div { + p { + "Change theme..." + } + ul.theme { + @for color_scheme in ColorScheme::iter() { + @if active_color_scheme == color_scheme { + li.active { + (color_scheme_link(sort_method, sort_order, color_scheme, default_color_scheme, serve_path)) + } + } @else { + li { + (color_scheme_link(sort_method, sort_order, color_scheme, default_color_scheme, serve_path)) } } } @@ -429,64 +439,75 @@ fn css(color_scheme: ColorScheme) -> Markup { }} nav {{ padding: 0 5rem; + display: flex; + justify-content: flex-end; }} - nav ul {{ - text-align: right; - list-style: none; - margin: 0; - padding: 0; - }} - nav ul li {{ - display: block; - transition-duration: 0.5s; - float: right; + nav > div {{ position: relative; + margin-left: 0.5rem; + }} + nav p {{ padding: 0.5rem 1rem; - background: {switch_theme_background}; width: 8rem; text-align: center; + background: {switch_theme_background}; + color: {change_theme_link_color}; + }} + nav p + * {{ + display: none; + position: absolute; + left: 0; + right: 0; + top: 100%; + animation: show 0.5s ease; + }} + @keyframes show {{ + from {{ + opacity: 0; + }} + to {{ + opacity: 1; + }} }} - nav ul li:hover {{ + nav > div::hover p {{ cursor: pointer; - text-decoration: none; - color: {change_theme_link_color} + color: {switch_theme_link_color}; }} - nav ul li a:hover {{ - text-decoration: none; - color: {change_theme_link_color_hover}; + nav > div:hover p + * {{ + display: block; + border-top: 1px solid {switch_theme_border}; }} - nav ul li ul {{ - visibility: hidden; - opacity: 0; - position: absolute; - transition: all 0.5s ease; - margin-top: 0.5rem; - left: 0; - display: none; - text-align: center; + nav .qrcode {{ + padding: 0.5rem; + background: {switch_theme_background}; }} - nav ul li:hover > ul, - nav ul li ul:hover {{ - visibility: visible; - opacity: 1; + nav .qrcode img {{ display: block; }} - nav ul li ul li:first-of-type {{ - border-top: 1px solid {switch_theme_border}; + nav .theme {{ + margin: 0; + padding: 0; + list-style-type: none; }} - nav ul li ul li {{ - clear: both; - width: 8rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; + nav .theme li {{ + width: 100%; + background: {switch_theme_background}; }} - nav ul li ul li a:hover {{ - text-decoration: underline; + nav .theme li a {{ + display: block; + width: 100%; + padding: 0.5rem 0; + text-align: center; + color: {switch_theme_link_color}; }} - nav ul li a, nav ul li ul li a, nav ul li a:visited, nav ul li ul li a:visited {{ - color: {switch_theme_link_color} + nav .theme li a:visited {{ + color: {switch_theme_link_color}; + }} + nav .theme li a::hover {{ + text-decoration: underline; + color: {change_theme_link_color_hover}; }} - nav ul li ul li.active a {{ + nav .theme li.active a {{ font-weight: bold; color: {switch_theme_active}; }} |