aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock20
-rw-r--r--Cargo.toml2
-rw-r--r--src/archive.rs13
-rw-r--r--src/args.rs18
-rw-r--r--src/listing.rs70
-rw-r--r--src/main.rs8
-rw-r--r--src/renderer.rs730
-rw-r--r--src/themes.rs257
8 files changed, 871 insertions, 247 deletions
diff --git a/Cargo.lock b/Cargo.lock
index db2959b..dec9413 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -781,6 +781,8 @@ dependencies = [
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tar 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)",
"yansi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1342,6 +1344,22 @@ dependencies = [
]
[[package]]
+name = "strum"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "strum_macros"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "syn"
version = "0.15.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2029,6 +2047,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3d0760c312538987d363c36c42339b55f5ee176ea8808bbe4543d484a291c8d1"
"checksum structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "528aeb7351d042e6ffbc2a6fb76a86f9b622fdf7c25932798e7a82cb03bc94c6"
+"checksum strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1c33039533f051704951680f1adfd468fd37ac46816ded0d9ee068e60f05f"
+"checksum strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "47cd23f5c7dee395a00fa20135e2ec0fffcdfa151c56182966d7a3261343432e"
"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2"
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
"checksum tar 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c2167ff53da2a661702b3299f71a91b61b1dffef36b4b2884b1f9c67254c0133"
diff --git a/Cargo.toml b/Cargo.toml
index 5384ee7..94b7455 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -43,3 +43,5 @@ futures = "0.1.26"
libflate = "0.1.21"
failure = "0.1.5"
log = "0.4.6"
+strum = "0.15.0"
+strum_macros = "0.15.0" \ No newline at end of file
diff --git a/src/archive.rs b/src/archive.rs
index 206d252..4703c0d 100644
--- a/src/archive.rs
+++ b/src/archive.rs
@@ -5,26 +5,21 @@ use libflate::gzip::Encoder;
use serde::Deserialize;
use std::io;
use std::path::PathBuf;
+use strum_macros::{Display, EnumIter, EnumString};
use tar::Builder;
use crate::errors;
/// Available compression methods
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Deserialize, Clone, EnumIter, EnumString, Display)]
+#[serde(rename_all = "snake_case")]
+#[strum(serialize_all = "snake_case")]
pub enum CompressionMethod {
/// TAR GZ
- #[serde(alias = "targz")]
TarGz,
}
impl CompressionMethod {
- pub fn to_string(&self) -> String {
- match &self {
- CompressionMethod::TarGz => "targz",
- }
- .to_string()
- }
-
pub fn extension(&self) -> String {
match &self {
CompressionMethod::TarGz => "tar.gz",
diff --git a/src/args.rs b/src/args.rs
index bb52824..516e0b6 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -3,13 +3,14 @@ use std::path::PathBuf;
use structopt::StructOpt;
use crate::auth;
+use crate::themes;
/// Possible characters for random routes
const ROUTE_ALPHABET: [char; 16] = [
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f',
];
-#[derive(StructOpt, Debug)]
+#[derive(StructOpt)]
#[structopt(
name = "miniserve",
raw(global_settings = "&[structopt::clap::AppSettings::ColoredHelp]")
@@ -48,6 +49,18 @@ struct CLIArgs {
#[structopt(short = "P", long = "no-symlinks")]
no_symlinks: bool,
+ /// Default color scheme
+ #[structopt(
+ short = "c",
+ long = "color-scheme",
+ default_value = "Squirrel",
+ raw(
+ possible_values = "&themes::ColorScheme::variants()",
+ case_insensitive = "true",
+ )
+ )]
+ color_scheme: themes::ColorScheme,
+
/// Enable file uploading
#[structopt(short = "u", long = "upload-files")]
file_upload: bool,
@@ -97,6 +110,8 @@ pub fn parse_args() -> crate::MiniserveConfig {
None
};
+ let default_color_scheme = args.color_scheme;
+
let path_explicitly_chosen = args.path.is_some();
crate::MiniserveConfig {
@@ -108,6 +123,7 @@ pub fn parse_args() -> crate::MiniserveConfig {
path_explicitly_chosen,
no_symlinks: args.no_symlinks,
random_route,
+ default_color_scheme,
overwrite_files: args.overwrite_files,
file_upload: args.file_upload,
}
diff --git a/src/listing.rs b/src/listing.rs
index 4a0927b..fdabc48 100644
--- a/src/listing.rs
+++ b/src/listing.rs
@@ -7,68 +7,51 @@ use serde::Deserialize;
use std::io;
use std::path::Path;
use std::time::SystemTime;
+use strum_macros::{Display, EnumString};
use crate::archive;
use crate::errors;
use crate::renderer;
+use crate::themes;
/// Query parameters
-#[derive(Debug, Deserialize)]
+#[derive(Deserialize)]
struct QueryParameters {
sort: Option<SortingMethod>,
order: Option<SortingOrder>,
download: Option<archive::CompressionMethod>,
+ theme: Option<themes::ColorScheme>,
}
/// Available sorting methods
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Deserialize, Clone, EnumString, Display)]
+#[serde(rename_all = "snake_case")]
+#[strum(serialize_all = "snake_case")]
pub enum SortingMethod {
/// Sort by name
- #[serde(alias = "name")]
Name,
/// Sort by size
- #[serde(alias = "size")]
Size,
/// Sort by last modification date (natural sort: follows alphanumerical order)
- #[serde(alias = "date")]
Date,
}
-impl SortingMethod {
- pub fn to_string(&self) -> String {
- match &self {
- SortingMethod::Name => "name",
- SortingMethod::Size => "size",
- SortingMethod::Date => "date",
- }
- .to_string()
- }
-}
-
/// Available sorting orders
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Deserialize, Clone, EnumString, Display)]
pub enum SortingOrder {
/// Ascending order
#[serde(alias = "asc")]
+ #[strum(serialize = "asc")]
Ascending,
/// Descending order
#[serde(alias = "desc")]
+ #[strum(serialize = "desc")]
Descending,
}
-impl SortingOrder {
- pub fn to_string(&self) -> String {
- match &self {
- SortingOrder::Ascending => "asc",
- SortingOrder::Descending => "desc",
- }
- .to_string()
- }
-}
-
#[derive(PartialEq)]
/// Possible entry types
pub enum EntryType {
@@ -77,6 +60,9 @@ pub enum EntryType {
/// Entry is a file
File,
+
+ /// Entry is a symlink
+ Symlink,
}
/// Entry
@@ -114,9 +100,20 @@ impl Entry {
}
}
+ /// Returns wether the entry is a directory
pub fn is_dir(&self) -> bool {
self.entry_type == EntryType::Directory
}
+
+ /// Returns wether the entry is a file
+ pub fn is_file(&self) -> bool {
+ self.entry_type == EntryType::File
+ }
+
+ /// Returns wether the entry is a symlink
+ pub fn is_symlink(&self) -> bool {
+ self.entry_type == EntryType::Symlink
+ }
}
pub fn file_handler(req: &HttpRequest<crate::MiniserveConfig>) -> Result<fs::NamedFile> {
@@ -132,6 +129,7 @@ pub fn directory_listing<S>(
skip_symlinks: bool,
file_upload: bool,
random_route: Option<String>,
+ default_color_scheme: themes::ColorScheme,
upload_route: String,
) -> Result<HttpResponse, io::Error> {
let title = format!("Index of {}", req.path());
@@ -144,15 +142,16 @@ pub fn directory_listing<S>(
Err(_) => base.to_path_buf(),
};
- let (sort_method, sort_order, download) =
+ let (sort_method, sort_order, download, color_scheme) =
if let Ok(query) = Query::<QueryParameters>::extract(req) {
(
query.sort.clone(),
query.order.clone(),
query.download.clone(),
+ query.theme.clone(),
)
} else {
- (None, None, None)
+ (None, None, None, None)
};
let mut entries: Vec<Entry> = Vec::new();
@@ -180,7 +179,15 @@ pub fn directory_listing<S>(
Err(_) => None,
};
- if metadata.is_dir() {
+ if metadata.file_type().is_symlink() {
+ entries.push(Entry::new(
+ file_name,
+ EntryType::Symlink,
+ file_url,
+ None,
+ last_modification_date,
+ ));
+ } else if metadata.is_dir() {
entries.push(Entry::new(
file_name,
EntryType::Directory,
@@ -233,6 +240,8 @@ pub fn directory_listing<S>(
}
}
+ let color_scheme = color_scheme.unwrap_or(default_color_scheme);
+
if let Some(compression_method) = &download {
log::info!(
"Creating an archive ({extension}) of {path}...",
@@ -271,6 +280,7 @@ pub fn directory_listing<S>(
page_parent,
sort_method,
sort_order,
+ color_scheme,
file_upload,
&upload_route,
&current_dir.display().to_string(),
diff --git a/src/main.rs b/src/main.rs
index 0ca2fdf..a703b00 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -17,8 +17,9 @@ mod errors;
mod file_upload;
mod listing;
mod renderer;
+mod themes;
-#[derive(Clone, Debug)]
+#[derive(Clone)]
/// Configuration of the Miniserve application
pub struct MiniserveConfig {
/// Enable verbose mode
@@ -45,6 +46,9 @@ pub struct MiniserveConfig {
/// Enable random route generation
pub random_route: Option<String>,
+ /// Default color scheme
+ pub default_color_scheme: themes::ColorScheme,
+
/// Enable file upload
pub file_upload: bool,
@@ -187,6 +191,7 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> {
let path = &app.state().path;
let no_symlinks = app.state().no_symlinks;
let random_route = app.state().random_route.clone();
+ let default_color_scheme = app.state().default_color_scheme.clone();
let file_upload = app.state().file_upload;
upload_route = match app.state().random_route.clone() {
Some(random_route) => format!("/{}/upload", random_route),
@@ -207,6 +212,7 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> {
no_symlinks,
file_upload,
random_route.clone(),
+ default_color_scheme.clone(),
u_r.clone(),
)
}),
diff --git a/src/renderer.rs b/src/renderer.rs
index c166bc6..cf30d4a 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -2,9 +2,11 @@ use chrono::{DateTime, Duration, Utc};
use chrono_humanize::{Accuracy, HumanTime, Tense};
use maud::{html, Markup, PreEscaped, DOCTYPE};
use std::time::SystemTime;
+use strum::IntoEnumIterator;
use crate::archive;
use crate::listing;
+use crate::themes;
/// Renders the file listing
pub fn page(
@@ -14,51 +16,124 @@ pub fn page(
page_parent: Option<String>,
sort_method: Option<listing::SortingMethod>,
sort_order: Option<listing::SortingOrder>,
+ color_scheme: themes::ColorScheme,
file_upload: bool,
upload_route: &str,
current_dir: &str,
) -> Markup {
html! {
- (page_header(page_title))
- body id="dropContainer" {
- span #top { }
- h1 { (page_title) }
+ (page_header(page_title, &color_scheme, file_upload))
+ body#drop-container {
@if file_upload {
- form id="file_submit" action={(upload_route) "?path=" (current_dir)} method="POST" enctype="multipart/form-data" {
- p { "Select file to upload or drag it into the window" }
- input type="file" name="file_to_upload" id="fileInput" {}
- input type="submit" value="Upload file" {}
+ div.drag-form {
+ div.drag-title {
+ h1 { "Drop your file here to upload it" }
+ }
}
}
- div.download {
- (archive_button(archive::CompressionMethod::TarGz))
- }
- table {
- thead {
- th { (build_link("name", "Name", &sort_method, &sort_order)) }
- th { (build_link("size", "Size", &sort_method, &sort_order)) }
- th { (build_link("date", "Last modification", &sort_method, &sort_order)) }
+ (color_scheme_selector(&sort_method, &sort_order, &color_scheme))
+ div.container {
+ span#top { }
+ h1.title { (page_title) }
+ div.toolbar {
+ div.download {
+ @for compression_method in archive::CompressionMethod::iter() {
+ (archive_button(compression_method))
+ }
+ }
+ @if file_upload {
+ div.upload {
+ form id="file_submit" action={(upload_route) "?path=" (current_dir)} method="POST" enctype="multipart/form-data" {
+ p { "Select a file to upload or drag it anywhere into the window" }
+ div {
+ input#file-input type="file" name="file_to_upload" {}
+ button type="submit" { "Upload file" }
+ }
+ }
+ }
+ }
}
- tbody {
- @if !is_root {
- @if let Some(parent) = page_parent {
- tr {
- td colspan="3" {
- span.chevron { (chevron_left()) }
- a.root href=(parent) {
- "Parent directory"
+ table {
+ thead {
+ th { (build_link("name", "Name", &sort_method, &sort_order, &color_scheme)) }
+ th { (build_link("size", "Size", &sort_method, &sort_order, &color_scheme)) }
+ th { (build_link("date", "Last modification", &sort_method, &sort_order, &color_scheme)) }
+ }
+ tbody {
+ @if !is_root {
+ @if let Some(parent) = page_parent {
+ tr {
+ td colspan="3" {
+ span.root-chevron { (chevron_left()) }
+ a.root href=(parametrized_link(&parent, &sort_method, &sort_order, &color_scheme)) {
+ "Parent directory"
+ }
}
}
}
}
+ @for entry in entries {
+ (entry_row(entry, &sort_method, &sort_order, &color_scheme))
+ }
}
- @for entry in entries {
- (entry_row(entry))
+ }
+ a.back href="#top" {
+ (arrow_up())
+ }
+ }
+ }
+ }
+}
+
+/// Partial: color scheme selector
+fn color_scheme_selector(
+ sort_method: &Option<listing::SortingMethod>,
+ sort_order: &Option<listing::SortingOrder>,
+ active_color_scheme: &themes::ColorScheme,
+) -> Markup {
+ html! {
+ nav {
+ ul {
+ li {
+ a.change-theme href="#" title="Change theme" {
+ "Change theme..."
+ }
+ ul {
+ @for color_scheme in themes::ColorScheme::iter() {
+ @if active_color_scheme == &color_scheme {
+ li.active {
+ (color_scheme_link(&sort_method, &sort_order, &color_scheme))
+ }
+ } @else {
+ li {
+ (color_scheme_link(&sort_method, &sort_order, &color_scheme))
+ }
+ }
+ }
}
}
}
- a.back href="#top" {
- (arrow_up())
+ }
+ }
+}
+
+/// Partial: color scheme link
+fn color_scheme_link(
+ sort_method: &Option<listing::SortingMethod>,
+ sort_order: &Option<listing::SortingOrder>,
+ color_scheme: &themes::ColorScheme,
+) -> Markup {
+ let link = parametrized_link("", &sort_method, &sort_order, &color_scheme);
+ let title = format!("Switch to {} theme", color_scheme);
+
+ html! {
+ a href=(link) title=(title) {
+ (color_scheme)
+ " "
+ @if color_scheme.is_dark() {
+ "(dark)"
+ } @else {
+ "(light)"
}
}
}
@@ -66,7 +141,7 @@ pub fn page(
/// Partial: archive button
fn archive_button(compress_method: archive::CompressionMethod) -> Markup {
- let link = format!("?download={}", compress_method.to_string());
+ let link = format!("?download={}", compress_method);
let text = format!("Download .{}", compress_method.extension());
html! {
@@ -76,12 +151,35 @@ fn archive_button(compress_method: archive::CompressionMethod) -> Markup {
}
}
+/// If they are set, adds query parameters to links to keep them across pages
+fn parametrized_link(
+ link: &str,
+ sort_method: &Option<listing::SortingMethod>,
+ sort_order: &Option<listing::SortingOrder>,
+ color_scheme: &themes::ColorScheme,
+) -> String {
+ if let Some(method) = sort_method {
+ if let Some(order) = sort_order {
+ return format!(
+ "{}?sort={}&order={}&theme={}",
+ link,
+ method,
+ order,
+ color_scheme.to_slug()
+ );
+ }
+ }
+
+ format!("{}?theme={}", link.to_string(), color_scheme.to_slug())
+}
+
/// Partial: table header link
fn build_link(
name: &str,
title: &str,
sort_method: &Option<listing::SortingMethod>,
sort_order: &Option<listing::SortingOrder>,
+ color_scheme: &themes::ColorScheme,
) -> Markup {
let mut link = format!("?sort={}&order=asc", name);
let mut help = format!("Sort by {} in ascending order", name);
@@ -104,48 +202,43 @@ fn build_link(
html! {
span class=(class) {
span.chevron { (chevron) }
- a href=(link) title=(help) { (title) }
+ a href=(format!("{}&theme={}", &link, color_scheme.to_slug())) title=(help) { (title) }
}
}
}
/// Partial: row for an entry
-fn entry_row(entry: listing::Entry) -> Markup {
+fn entry_row(
+ entry: listing::Entry,
+ sort_method: &Option<listing::SortingMethod>,
+ sort_order: &Option<listing::SortingOrder>,
+ color_scheme: &themes::ColorScheme,
+) -> Markup {
html! {
tr {
td {
p {
@if entry.is_dir() {
- a.directory href=(entry.link) {
+ a.directory href=(parametrized_link(&entry.link, &sort_method, &sort_order, &color_scheme)) {
(entry.name) "/"
}
- } @else {
- a.file href=(entry.link) {
- (entry.name)
+ } @else if entry.is_file() {
+ div.file-entry {
+ a.file href=(&entry.link) {
+ (entry.name)
+ }
+ @if let Some(size) = entry.size {
+ span.mobile-info.size {
+ (size)
+ }
+ }
}
- }
- }
- @if !entry.is_dir() {
- @if let Some(size) = entry.size {
- span .mobile-info {
- strong { "Size: " }
- (size)
- (br())
+ } @else if entry.is_symlink() {
+ a.symlink href=(parametrized_link(&entry.link, &sort_method, &sort_order, &color_scheme)) {
+ (entry.name) span.symlink-symbol { "⇢" }
}
}
}
- span .mobile-info {
- @if let Some(modification_date) = convert_to_utc(entry.last_modification_date) {
- strong { "Last modification: " }
- (modification_date.0) " "
- (modification_date.1) " "
- }
- @if let Some(modification_timer) = humanize_systemtime(entry.last_modification_date) {
- span .history { "(" (modification_timer) ")" }
- (br())
- }
-
- }
}
td {
@if let Some(size) = entry.size {
@@ -156,14 +249,13 @@ fn entry_row(entry: listing::Entry) -> Markup {
@if let Some(modification_date) = convert_to_utc(entry.last_modification_date) {
span {
(modification_date.0) " "
- }
- span {
+ span.at { " at " }
(modification_date.1) " "
}
}
@if let Some(modification_timer) = humanize_systemtime(entry.last_modification_date) {
- span {
- "(" (modification_timer) ")"
+ span.history {
+ (modification_timer)
}
}
}
@@ -172,165 +264,377 @@ fn entry_row(entry: listing::Entry) -> Markup {
}
/// Partial: CSS
-fn css() -> Markup {
- (PreEscaped(r#"
- html {
+fn css(color_scheme: &themes::ColorScheme) -> Markup {
+ let theme = color_scheme.clone().get_theme();
+
+ let css = format!("
+ html {{
font-smoothing: antialiased;
text-rendering: optimizeLegibility;
- }
- body {
+ width: 100%;
+ height: 100%;
+ }}
+ body {{
margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,"Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto,\"Helvetica Neue\", Helvetica, Arial, sans-serif;
font-weight: 300;
- color: #444444;
- padding: 0.125rem;
- }
- strong {
+ color: {text_color};
+ background: {background};
+ position: relative;
+ min-height: 100%;
+ }}
+ .container {{
+ padding: 1.5rem 5rem;
+ }}
+ a {{
+ text-decoration: none;
+ }}
+ a.root, a.root:visited, .root-chevron {{
font-weight: bold;
- }
- p {
+ color: {root_link_color};
+ }}
+ a:hover {{
+ text-decoration: underline;
+ }}
+ a.directory, a.directory:visited {{
+ font-weight: bold;
+ color: {directory_link_color};
+ }}
+ a.file, a.file:visited {{
+ color: {file_link_color};
+ }}
+ a.symlink, a.symlink:visited {{
+ color: {symlink_link_color};
+ }}
+ a.directory:hover {{
+ color: {directory_link_color};
+ }}
+ a.file:hover {{
+ color: {file_link_color};
+ }}
+ a.symlink:hover {{
+ color: {symlink_link_color};
+ }}
+ .symlink-symbol {{
+ display: inline-block;
+ border: 1px solid {symlink_link_color};
+ margin-left: 0.5rem;
+ border-radius: .2rem;
+ padding: 0 0.1rem;
+ }}
+ nav {{
+ padding: 0 5rem;
+ }}
+ nav ul {{
+ text-align: right;
+ list-style: none;
margin: 0;
padding: 0;
- }
- h1 {
+ }}
+ nav ul li {{
+ display: block;
+ transition-duration: 0.5s;
+ float: right;
+ position: relative;
+ padding: 0.5rem 1rem;
+ background: {switch_theme_background};
+ width: 8rem;
+ text-align: center;
+ }}
+ nav ul li:hover {{
+ cursor: pointer;
+ text-decoration: none;
+ color: {change_theme_link_color}
+ }}
+ nav ul li a:hover {{
+ text-decoration: none;
+ color: {change_theme_link_color_hover};
+ }}
+ 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 ul li:hover > ul,
+ nav ul li ul:hover {{
+ visibility: visible;
+ opacity: 1;
+ display: block;
+ }}
+ nav ul li ul li:first-of-type {{
+ border-top: 1px solid {switch_theme_border};
+ }}
+ nav ul li ul li {{
+ clear: both;
+ width: 8rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ }}
+ nav ul li ul li a:hover {{
+ text-decoration: underline;
+ }}
+ 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 ul li ul li.active a {{
+ font-weight: bold;
+ color: {switch_theme_active};
+ }}
+ p {{
+ margin: 0;
+ padding: 0;
+ }}
+ h1 {{
+ margin-top: 0;
font-size: 1.5rem;
- }
- table {
+ }}
+ table {{
margin-top: 2rem;
width: 100%;
- background: white;
border: 0;
table-layout: auto;
- }
- table thead {
- background: #efefef;
- }
- table tr th,
- table tr td {
+ background: {table_background};
+ }}
+ table thead tr th,
+ table tbody tr td {{
padding: 0.5625rem 0.625rem;
font-size: 0.875rem;
- color: #777c82;
+ color: {table_text_color};
text-align: left;
line-height: 1.125rem;
width: 33.333%;
- }
- table tr th {
+ }}
+ table thead tr th {{
padding: 0.5rem 0.625rem 0.625rem;
font-weight: bold;
- color: #444444;
- }
- table tr:nth-child(even) {
- background: #f6f6f6;
- }
- table tr:hover {
- background: #deeef7a6;
- }
- a {
- text-decoration: none;
- color: #3498db;
- }
- a.root, a.root:visited {
- font-weight: bold;
- color: #777c82;
- }
- a.directory {
- font-weight: bold;
- }
- a:hover {
- text-decoration: underline;
- }
- a:visited {
- color: #8e44ad;
- }
- td.date-cell {
+ }}
+ table tbody tr:nth-child(odd) {{
+ background: {odd_row_background};
+ }}
+ table tbody tr:nth-child(even) {{
+ background: {even_row_background};
+ }}
+ table thead {{
+ background: {table_header_background};
+ }}
+ table tbody tr:hover {{
+ background: {active_row_color};
+ }}
+ td.date-cell {{
display: flex;
width: calc(100% - 1.25rem);
- }
- td.date-cell span:first-of-type,
- td.date-cell span:nth-of-type(2) {
- flex-basis:4.5rem;
- }
- td.date-cell span:nth-of-type(3), .history {
- color: #c5c5c5;
- }
- .file, .directory {
- display: block;
- }
- .mobile-info {
+ justify-content: space-between;
+ }}
+ .at {{
+ color: {at_color};
+ }}
+ .history {{
+ color: {date_text_color};
+ }}
+ .file-entry {{
+ display: flex;
+ justify-content: space-between;
+ }}
+ span.size {{
+ border-radius: 1rem;
+ background: {size_background_color};
+ padding: 0 0.25rem;
+ font-size: 0.7rem;
+ color: {size_text_color}
+ }}
+ .mobile-info {{
display: none;
- }
- th a, th a:visited, .chevron {
- color: #777c82;
- }
- .chevron {
+ }}
+ th a, th a:visited, .chevron {{
+ color: {table_header_text_color};
+ }}
+ .chevron, .root-chevron {{
margin-right: .5rem;
font-size: 1.2em;
font-weight: bold;
- }
- th span.active a, th span.active span {
- color: #444444;
- }
- .back {
+ }}
+ th span.active a, th span.active span {{
+ color: {table_header_active_color};
+ }}
+ .back {{
position: fixed;
- bottom: 1.1rem;
- right: 0.625rem;
- background: #e0e0e0;
+ bottom: 3rem;
+ right: 3.75rem;
+ background: {back_button_background};
border-radius: 100%;
box-shadow: 0 0 8px -4px #888888;
- opacity: 0.8;
- padding: 1rem 1.1rem;
- color: #444444;
- }
- .back:visited {
- color: #444444;
- }
- .back:hover {
- color: #3498db;
+ padding: 1.4rem 1.5rem;
+ color: {back_button_link_color};
+ display: none;
+ }}
+ .back:visited {{
+ color: {back_button_link_color};
+ }}
+ .back:hover {{
+ color: {back_button_link_color_hover};
+ font-weight: bold;
text-decoration: none;
- }
- .download {
+ background: {back_button_background_hover};
+ }}
+ .toolbar {{
display: flex;
+ justify-content: space-between;
flex-wrap: wrap;
- margin-top: .5rem;
+ }}
+ .download {{
+ margin-top: 1rem;
padding: 0.125rem;
- }
- .download a, .download a:visited {
- color: #3498db;
- }
- .download a {
- background: #efefef;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ flex-wrap: wrap;
+ }}
+ .download a, .download a:visited {{
+ color: {download_button_link_color};
+ }}
+ .download a {{
+ background: {download_button_background};
padding: 0.5rem;
border-radius: 0.2rem;
- margin-top: 1rem;
- }
- .download a:hover {
- background: #deeef7a6;
- }
- .download a:not(:last-of-type) {
+ }}
+ .download a:hover {{
+ background: {download_button_background_hover};
+ color: {download_button_link_color_hover};
+ }}
+ .download a:not(:last-of-type) {{
margin-right: 1rem;
- }
- .drag_hover {
- box-shadow: inset 0 25px 40px #aae;
- }
- @media (max-width: 600px) {
- h1 {
- font-size: 1.375em;
- }
- td:not(:nth-child(1)), th:not(:nth-child(1)){
+ }}
+ .upload {{
+ margin-top: 1rem;
+ display: flex;
+ justify-content: flex-end;
+ }}
+ .upload p {{
+ font-size: 0.8rem;
+ margin-bottom: 1rem;
+ color: {upload_text_color};
+ }}
+ .upload form {{
+ padding: 1rem;
+ border: 1px solid {upload_form_border_color};
+ background: {upload_form_background};
+ }}
+ .upload button {{
+ background: {upload_button_background};
+ padding: 0.5rem;
+ border-radius: 0.2rem;
+ color: {upload_button_text_color};
+ border: none;
+ }}
+ .upload div {{
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ }}
+ .drag-form {{
+ display: none;
+ background: {drag_background};
+ position: absolute;
+ border: 0.5rem dashed {drag_border_color};
+ width: calc(100% - 1rem);
+ height: calc(100% - 1rem);
+ text-align: center;
+ z-index: 2;
+ }}
+ .drag-title {{
+ position: fixed;
+ color: {drag_text_color};
+ top: 50%;
+ width: 100%;
+ text-align: center;
+ }}
+ @media (max-width: 760px) {{
+ nav {{
+ padding: 0 2.5rem;
+ }}
+ .container {{
+ padding: 1.5rem 2.5rem;
+ }}
+ h1 {{
+ font-size: 1.4em;
+ }}
+ td:not(:nth-child(1)), th:not(:nth-child(1)){{
display: none;
- }
- .mobile-info {
+ }}
+ .mobile-info {{
display: block;
- }
- .file, .directory{
- padding-bottom: 1rem;
- }
- }
- @media (max-width: 400px) {
- h1 {
+ }}
+ .back {{
+ display: initial;
+ }}
+ .back {{
+ right: 1.5rem;
+ }}
+ }}
+ @media (max-width: 600px) {{
+ h1 {{
font-size: 1.375em;
- }
- }"#.to_string()))
+ }}
+ }}
+ @media (max-width: 400px) {{
+ nav {{
+ padding: 0 0.5rem;
+ }}
+ .container {{
+ padding: 0.5rem;
+ }}
+ h1 {{
+ font-size: 1.375em;
+ }}
+ .back {{
+ right: 1.5rem;
+ }}
+ }}", background = theme.background,
+ text_color = theme.text_color,
+ directory_link_color = theme.directory_link_color,
+ file_link_color = theme.file_link_color,
+ symlink_link_color = theme.symlink_link_color,
+ table_background = theme.table_background,
+ table_text_color = theme.table_text_color,
+ table_header_background = theme.table_header_background,
+ table_header_text_color = theme.table_header_text_color,
+ table_header_active_color = theme.table_header_active_color,
+ active_row_color = theme.active_row_color,
+ odd_row_background = theme.odd_row_background,
+ even_row_background = theme.even_row_background,
+ root_link_color = theme.root_link_color,
+ download_button_background = theme.download_button_background,
+ download_button_background_hover = theme.download_button_background_hover,
+ download_button_link_color = theme.download_button_link_color,
+ download_button_link_color_hover = theme.download_button_link_color_hover,
+ back_button_background = theme.back_button_background,
+ back_button_background_hover = theme.back_button_background_hover,
+ back_button_link_color = theme.back_button_link_color,
+ back_button_link_color_hover = theme.back_button_link_color_hover,
+ date_text_color = theme.date_text_color,
+ at_color = theme.at_color,
+ switch_theme_background = theme.switch_theme_background,
+ switch_theme_link_color = theme.switch_theme_link_color,
+ switch_theme_active = theme.switch_theme_active,
+ switch_theme_border = theme.switch_theme_border,
+ change_theme_link_color = theme.change_theme_link_color,
+ change_theme_link_color_hover = theme.change_theme_link_color_hover,
+ upload_text_color = theme.upload_text_color,
+ upload_form_border_color = theme.upload_form_border_color,
+ upload_form_background = theme.upload_form_background,
+ upload_button_background = theme.upload_button_background,
+ upload_button_text_color = theme.upload_button_text_color,
+ drag_background = theme.drag_background,
+ drag_border_color = theme.drag_border_color,
+ drag_text_color = theme.drag_text_color,
+ size_background_color = theme.size_background_color,
+ size_text_color = theme.size_text_color);
+ (PreEscaped(css))
}
/// Partial: up arrow
@@ -338,11 +642,6 @@ fn arrow_up() -> Markup {
(PreEscaped("⇪".to_string()))
}
-/// Partial: new line
-fn br() -> Markup {
- (PreEscaped("<br>".to_string()))
-}
-
/// Partial: chevron left
fn chevron_left() -> Markup {
(PreEscaped("◂".to_string()))
@@ -359,7 +658,7 @@ fn chevron_down() -> Markup {
}
/// Partial: page header
-fn page_header(page_title: &str) -> Markup {
+fn page_header(page_title: &str, color_scheme: &themes::ColorScheme, file_upload: bool) -> Markup {
html! {
(DOCTYPE)
html {
@@ -367,27 +666,46 @@ fn page_header(page_title: &str) -> Markup {
meta http-equiv="X-UA-Compatible" content="IE=edge";
meta name="viewport" content="width=device-width, initial-scale=1";
title { (page_title) }
- style { (css()) }
- (PreEscaped(r#"
- <script>
- window.onload = function() {
- dropContainer.ondragover = dropContainer.ondragenter = function(evt) {
- dropContainer.className = "drag_hover";
- evt.preventDefault();
- };
+ style { (css(&color_scheme)) }
+ @if file_upload {
+ (PreEscaped(r#"
+ <script>
+ window.onload = function() {
+ const dropContainer = document.querySelector('#drop-container');
+ const dragForm = document.querySelector('.drag-form');
+ const fileInput = document.querySelector('#file-input');
+ const collection = [];
- dropContainer.ondrop = function(evt) {
- fileInput.files = evt.dataTransfer.files;
- evt.preventDefault();
- file_submit.submit();
- };
+ dropContainer.ondragover = function(e) {
+ e.preventDefault();
+ }
+
+ dropContainer.ondragenter = function(e) {
+ e.preventDefault();
+ if (collection.length === 0) {
+ dragForm.style.display = 'initial';
+ }
+ collection.push(e.target);
+ };
- dropContainer.ondragleave = function() {
- dropContainer.className = "";
+ dropContainer.ondragleave = function(e) {
+ e.preventDefault();
+ collection.splice(collection.indexOf(e.target), 1);
+ if (collection.length === 0) {
+ dragForm.style.display = 'none';
+ }
+ };
+
+ dropContainer.ondrop = function(e) {
+ e.preventDefault();
+ fileInput.files = e.dataTransfer.files;
+ file_submit.submit();
+ dragForm.style.display = 'none';
+ };
}
- }
- </script>
- "#))
+ </script>
+ "#))
+ }
}
}
}
@@ -398,7 +716,7 @@ fn page_header(page_title: &str) -> Markup {
fn convert_to_utc(src_time: Option<SystemTime>) -> Option<(String, String)> {
src_time.map(DateTime::<Utc>::from).map(|date_time| {
(
- date_time.format("%e %b").to_string(),
+ date_time.format("%b %e").to_string(),
date_time.format("%R").to_string(),
)
})
@@ -420,7 +738,7 @@ pub fn file_upload_error(error_description: &str, return_address: &str) -> Marku
h1 { "File uploading failed" }
p { (error_description) }
a href=(return_address) {
- "back"
+ "Go back to file listing"
}
}
}
diff --git a/src/themes.rs b/src/themes.rs
new file mode 100644
index 0000000..328a2e8
--- /dev/null
+++ b/src/themes.rs
@@ -0,0 +1,257 @@
+use serde::Deserialize;
+use structopt::clap::{_clap_count_exprs, arg_enum};
+use strum_macros::EnumIter;
+
+arg_enum! {
+ #[derive(PartialEq, Deserialize, Clone, EnumIter)]
+ #[serde(rename_all = "lowercase")]
+ pub enum ColorScheme {
+ Archlinux,
+ Zenburn,
+ Monokai,
+ Squirrel,
+ }
+}
+
+impl ColorScheme {
+ /// Returns the URL-compatible name of a color scheme
+ /// This must correspond to the name of the variant, in lowercase
+ /// See https://github.com/svenstaro/miniserve/pull/55 for explanations
+ pub fn to_slug(&self) -> String {
+ match &self {
+ ColorScheme::Archlinux => "archlinux",
+ ColorScheme::Zenburn => "zenburn",
+ ColorScheme::Monokai => "monokai",
+ ColorScheme::Squirrel => "squirrel",
+ }
+ .to_string()
+ }
+
+ /// Returns wether a color scheme is dark
+ pub fn is_dark(&self) -> bool {
+ match &self {
+ ColorScheme::Archlinux => true,
+ ColorScheme::Zenburn => true,
+ ColorScheme::Monokai => true,
+ ColorScheme::Squirrel => false,
+ }
+ }
+
+ /// Retrieves the color palette associated to a color scheme
+ pub fn get_theme(self) -> Theme {
+ match self {
+ ColorScheme::Archlinux => Theme {
+ background: "#383c4a".to_string(),
+ text_color: "#fefefe".to_string(),
+ directory_link_color: "#03a9f4".to_string(),
+ file_link_color: "#ea95ff".to_string(),
+ symlink_link_color: "#ff9800".to_string(),
+ table_background: "#353946".to_string(),
+ table_text_color: "#eeeeee".to_string(),
+ table_header_background: "#5294e2".to_string(),
+ table_header_text_color: "#eeeeee".to_string(),
+ table_header_active_color: "#ffffff".to_string(),
+ active_row_color: "#5194e259".to_string(),
+ odd_row_background: "#404552".to_string(),
+ even_row_background: "#4b5162".to_string(),
+ root_link_color: "#abb2bb".to_string(),
+ download_button_background: "#ea95ff".to_string(),
+ download_button_background_hover: "#eea7ff".to_string(),
+ download_button_link_color: "#ffffff".to_string(),
+ download_button_link_color_hover: "#ffffff".to_string(),
+ back_button_background: "#ea95ff".to_string(),
+ back_button_background_hover: "#ea95ff".to_string(),
+ back_button_link_color: "#ffffff".to_string(),
+ back_button_link_color_hover: "#ffffff".to_string(),
+ date_text_color: "#9ebbdc".to_string(),
+ at_color: "#9ebbdc".to_string(),
+ switch_theme_background: "#4b5162".to_string(),
+ switch_theme_link_color: "#fefefe".to_string(),
+ switch_theme_active: "#ea95ff".to_string(),
+ switch_theme_border: "#6a728a".to_string(),
+ change_theme_link_color: "#fefefe".to_string(),
+ change_theme_link_color_hover: "#fefefe".to_string(),
+ upload_text_color: "#fefefe".to_string(),
+ upload_form_border_color: "#353946".to_string(),
+ upload_form_background: "#4b5162".to_string(),
+ upload_button_background: "#ea95ff".to_string(),
+ upload_button_text_color: "#ffffff".to_string(),
+ drag_background: "#3333338f".to_string(),
+ drag_border_color: "#fefefe".to_string(),
+ drag_text_color: "#fefefe".to_string(),
+ size_background_color: "#5294e2".to_string(),
+ size_text_color: "#fefefe".to_string(),
+ },
+ ColorScheme::Zenburn => Theme {
+ background: "#3f3f3f".to_string(),
+ text_color: "#efefef".to_string(),
+ directory_link_color: "#f0dfaf".to_string(),
+ file_link_color: "#87D6D5".to_string(),
+ symlink_link_color: "#FFCCEE".to_string(),
+ table_background: "#4a4949".to_string(),
+ table_text_color: "#efefef".to_string(),
+ table_header_background: "#7f9f7f".to_string(),
+ table_header_text_color: "#efefef".to_string(),
+ table_header_active_color: "#efef8f".to_string(),
+ active_row_color: "#7e9f7f9c".to_string(),
+ odd_row_background: "#777777".to_string(),
+ even_row_background: "#5a5a5a".to_string(),
+ root_link_color: "#dca3a3".to_string(),
+ download_button_background: "#cc9393".to_string(),
+ download_button_background_hover: "#dca3a3".to_string(),
+ download_button_link_color: "#efefef".to_string(),
+ download_button_link_color_hover: "#efefef".to_string(),
+ back_button_background: "#cc9393".to_string(),
+ back_button_background_hover: "#cc9393".to_string(),
+ back_button_link_color: "#efefef".to_string(),
+ back_button_link_color_hover: "#efefef".to_string(),
+ date_text_color: "#cfbfaf".to_string(),
+ at_color: "#cfbfaf".to_string(),
+ switch_theme_background: "#4a4949".to_string(),
+ switch_theme_link_color: "#efefef".to_string(),
+ switch_theme_active: "#efef8f".to_string(),
+ switch_theme_border: "#5a5a5a".to_string(),
+ change_theme_link_color: "#efefef".to_string(),
+ change_theme_link_color_hover: "#efefef".to_string(),
+ upload_text_color: "#efefef".to_string(),
+ upload_form_border_color: "#4a4949".to_string(),
+ upload_form_background: "#777777".to_string(),
+ upload_button_background: "#cc9393".to_string(),
+ upload_button_text_color: "#efefef".to_string(),
+ drag_background: "#3333338f".to_string(),
+ drag_border_color: "#efefef".to_string(),
+ drag_text_color: "#efefef".to_string(),
+ size_background_color: "#7f9f7f".to_string(),
+ size_text_color: "#efefef".to_string(),
+ },
+ ColorScheme::Monokai => Theme {
+ background: "#272822".to_string(),
+ text_color: "#F8F8F2".to_string(),
+ directory_link_color: "#F92672".to_string(),
+ file_link_color: "#A6E22E".to_string(),
+ symlink_link_color: "#FD971F".to_string(),
+ table_background: "#3B3A32".to_string(),
+ table_text_color: "#F8F8F0".to_string(),
+ table_header_background: "#75715E".to_string(),
+ table_header_text_color: "#F8F8F2".to_string(),
+ table_header_active_color: "#E6DB74".to_string(),
+ active_row_color: "#ae81fe3d".to_string(),
+ odd_row_background: "#3E3D32".to_string(),
+ even_row_background: "#49483E".to_string(),
+ root_link_color: "#66D9EF".to_string(),
+ download_button_background: "#AE81FF".to_string(),
+ download_button_background_hover: "#c6a6ff".to_string(),
+ download_button_link_color: "#F8F8F0".to_string(),
+ download_button_link_color_hover: "#F8F8F0".to_string(),
+ back_button_background: "#AE81FF".to_string(),
+ back_button_background_hover: "#AE81FF".to_string(),
+ back_button_link_color: "#F8F8F0".to_string(),
+ back_button_link_color_hover: "#F8F8F0".to_string(),
+ date_text_color: "#66D9EF".to_string(),
+ at_color: "#66D9EF".to_string(),
+ switch_theme_background: "#3B3A32".to_string(),
+ switch_theme_link_color: "#F8F8F2".to_string(),
+ switch_theme_active: "#A6E22E".to_string(),
+ switch_theme_border: "#49483E".to_string(),
+ change_theme_link_color: "#F8F8F2".to_string(),
+ change_theme_link_color_hover: "#F8F8F2".to_string(),
+ upload_text_color: "#F8F8F2".to_string(),
+ upload_form_border_color: "#3B3A32".to_string(),
+ upload_form_background: "#49483E".to_string(),
+ upload_button_background: "#AE81FF".to_string(),
+ upload_button_text_color: "#F8F8F0".to_string(),
+ drag_background: "#3333338f".to_string(),
+ drag_border_color: "#F8F8F2".to_string(),
+ drag_text_color: "#F8F8F2".to_string(),
+ size_background_color: "#75715E".to_string(),
+ size_text_color: "#F8F8F2".to_string(),
+ },
+ ColorScheme::Squirrel => Theme {
+ background: "#FFFFFF".to_string(),
+ text_color: "#323232".to_string(),
+ directory_link_color: "#d02474".to_string(),
+ file_link_color: "#0086B3".to_string(),
+ symlink_link_color: "#ED6A43".to_string(),
+ table_background: "#ffffff".to_string(),
+ table_text_color: "#323232".to_string(),
+ table_header_background: "#323232".to_string(),
+ table_header_text_color: "#F5F5F5".to_string(),
+ table_header_active_color: "#FFFFFF".to_string(),
+ active_row_color: "#f6f8fa".to_string(),
+ odd_row_background: "#fbfbfb".to_string(),
+ even_row_background: "#f2f2f2".to_string(),
+ root_link_color: "#323232".to_string(),
+ download_button_background: "#d02474".to_string(),
+ download_button_background_hover: "#f52d8a".to_string(),
+ download_button_link_color: "#FFFFFF".to_string(),
+ download_button_link_color_hover: "#FFFFFF".to_string(),
+ back_button_background: "#d02474".to_string(),
+ back_button_background_hover: "#d02474".to_string(),
+ back_button_link_color: "#FFFFFF".to_string(),
+ back_button_link_color_hover: "#FFFFFF".to_string(),
+ date_text_color: "#797979".to_string(),
+ at_color: "#797979".to_string(),
+ switch_theme_background: "#323232".to_string(),
+ switch_theme_link_color: "#F5F5F5".to_string(),
+ switch_theme_active: "#d02474".to_string(),
+ switch_theme_border: "#49483E".to_string(),
+ change_theme_link_color: "#F5F5F5".to_string(),
+ change_theme_link_color_hover: "#F5F5F5".to_string(),
+ upload_text_color: "#323232".to_string(),
+ upload_form_border_color: "#d2d2d2".to_string(),
+ upload_form_background: "#f2f2f2".to_string(),
+ upload_button_background: "#d02474".to_string(),
+ upload_button_text_color: "#FFFFFF".to_string(),
+ drag_background: "#3333338f".to_string(),
+ drag_border_color: "#ffffff".to_string(),
+ drag_text_color: "#ffffff".to_string(),
+ size_background_color: "#323232".to_string(),
+ size_text_color: "#FFFFFF".to_string(),
+ },
+ }
+ }
+}
+
+/// Describes a theme
+pub struct Theme {
+ pub background: String,
+ pub text_color: String,
+ pub directory_link_color: String,
+ pub file_link_color: String,
+ pub symlink_link_color: String,
+ pub table_background: String,
+ pub table_text_color: String,
+ pub table_header_background: String,
+ pub table_header_text_color: String,
+ pub table_header_active_color: String,
+ pub active_row_color: String,
+ pub odd_row_background: String,
+ pub even_row_background: String,
+ pub root_link_color: String,
+ pub download_button_background: String,
+ pub download_button_background_hover: String,
+ pub download_button_link_color: String,
+ pub download_button_link_color_hover: String,
+ pub back_button_background: String,
+ pub back_button_background_hover: String,
+ pub back_button_link_color: String,
+ pub back_button_link_color_hover: String,
+ pub date_text_color: String,
+ pub at_color: String,
+ pub switch_theme_background: String,
+ pub switch_theme_link_color: String,
+ pub switch_theme_active: String,
+ pub switch_theme_border: String,
+ pub change_theme_link_color: String,
+ pub change_theme_link_color_hover: String,
+ pub upload_text_color: String,
+ pub upload_form_border_color: String,
+ pub upload_form_background: String,
+ pub upload_button_background: String,
+ pub upload_button_text_color: String,
+ pub drag_background: String,
+ pub drag_border_color: String,
+ pub drag_text_color: String,
+ pub size_background_color: String,
+ pub size_text_color: String,
+}