Style the file-service #76
|
@ -1,8 +1,40 @@
|
||||||
use build_html::{self, Html, HtmlContainer};
|
use build_html::{self, Html, HtmlContainer};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct Attributes(Vec<(String, String)>);
|
||||||
|
|
||||||
|
/*
|
||||||
|
impl FromIterator<(String, String)> for Attributes {
|
||||||
|
fn from_iter<T>(iter: T) -> Self
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = (String, String)>,
|
||||||
|
{
|
||||||
|
Attributes(iter.collect::<Vec<(String, String)>>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<(&str, &str)> for Attributes {
|
||||||
|
fn from_iter<T>(iter: T) -> Self
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = (&str, &str)>,
|
||||||
|
{
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
impl ToString for Attributes {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.map(|(key, value)| format!("{}=\"{}\"", key, value))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Form {
|
pub struct Form {
|
||||||
classes: Option<String>,
|
|
||||||
path: String,
|
path: String,
|
||||||
method: String,
|
method: String,
|
||||||
encoding: Option<String>,
|
encoding: Option<String>,
|
||||||
|
@ -12,7 +44,6 @@ pub struct Form {
|
||||||
impl Form {
|
impl Form {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
classes: None,
|
|
||||||
path: "/".to_owned(),
|
path: "/".to_owned(),
|
||||||
method: "get".to_owned(),
|
method: "get".to_owned(),
|
||||||
encoding: None,
|
encoding: None,
|
||||||
|
@ -38,21 +69,16 @@ impl Form {
|
||||||
|
|
||||||
impl Html for Form {
|
impl Html for Form {
|
||||||
fn to_html_string(&self) -> String {
|
fn to_html_string(&self) -> String {
|
||||||
let classes = match self.classes {
|
|
||||||
Some(ref classes) => format!("class=\"{}\"", classes),
|
|
||||||
None => "".to_owned(),
|
|
||||||
};
|
|
||||||
let encoding = match self.encoding {
|
let encoding = match self.encoding {
|
||||||
Some(ref encoding) => format!("enctype=\"{encoding}\"", encoding = encoding),
|
Some(ref encoding) => format!("enctype=\"{encoding}\"", encoding = encoding),
|
||||||
None => "".to_owned(),
|
None => "".to_owned(),
|
||||||
};
|
};
|
||||||
format!(
|
format!(
|
||||||
"<form action=\"{path}\" method=\"{method}\" {encoding} {classes}>\n{elements}\n</form>\n",
|
"<form action=\"{path}\" method=\"{method}\" {encoding}\n{elements}\n</form>\n",
|
||||||
path = self.path,
|
path = self.path,
|
||||||
method = self.method,
|
method = self.method,
|
||||||
encoding = encoding,
|
encoding = encoding,
|
||||||
elements = self.elements.to_html_string(),
|
elements = self.elements.to_html_string(),
|
||||||
classes = classes,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +95,7 @@ pub struct Input {
|
||||||
name: String,
|
name: String,
|
||||||
id: Option<String>,
|
id: Option<String>,
|
||||||
value: Option<String>,
|
value: Option<String>,
|
||||||
attributes: Vec<(String, String)>,
|
attributes: Attributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Html for Input {
|
impl Html for Input {
|
||||||
|
@ -82,12 +108,7 @@ impl Html for Input {
|
||||||
Some(ref value) => format!("value=\"{}\"", value),
|
Some(ref value) => format!("value=\"{}\"", value),
|
||||||
None => "".to_owned(),
|
None => "".to_owned(),
|
||||||
};
|
};
|
||||||
let attrs = self
|
let attrs = self.attributes.to_string();
|
||||||
.attributes
|
|
||||||
.iter()
|
|
||||||
.map(|(key, value)| format!("{}=\"{}\"", key, value))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"<input type=\"{ty}\" name=\"{name}\" {id} {value} {attrs} />\n",
|
"<input type=\"{ty}\" name=\"{name}\" {id} {value} {attrs} />\n",
|
||||||
|
@ -107,7 +128,7 @@ impl Input {
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
id: None,
|
id: None,
|
||||||
value: None,
|
value: None,
|
||||||
attributes: vec![],
|
attributes: Attributes::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,10 +146,12 @@ impl Input {
|
||||||
mut self,
|
mut self,
|
||||||
values: impl IntoIterator<Item = (&'a str, &'a str)>,
|
values: impl IntoIterator<Item = (&'a str, &'a str)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.attributes = values
|
self.attributes = Attributes(
|
||||||
.into_iter()
|
values
|
||||||
.map(|(a, b)| (a.to_owned(), b.to_owned()))
|
.into_iter()
|
||||||
.collect::<Vec<(String, String)>>();
|
.map(|(a, b)| (a.to_owned(), b.to_owned()))
|
||||||
|
.collect::<Vec<(String, String)>>(),
|
||||||
|
);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,18 +224,37 @@ impl Html for Button {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
path: String,
|
path: String,
|
||||||
|
attributes: Attributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
pub fn new(path: &str) -> Self {
|
pub fn new(path: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
path: path.to_owned(),
|
path: path.to_owned(),
|
||||||
|
attributes: Attributes::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_attributes<'a>(
|
||||||
|
mut self,
|
||||||
|
values: impl IntoIterator<Item = (&'a str, &'a str)>,
|
||||||
|
) -> Self {
|
||||||
|
self.attributes = Attributes(
|
||||||
|
values
|
||||||
|
.into_iter()
|
||||||
|
.map(|(a, b)| (a.to_owned(), b.to_owned()))
|
||||||
|
.collect::<Vec<(String, String)>>(),
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Html for Image {
|
impl Html for Image {
|
||||||
fn to_html_string(&self) -> String {
|
fn to_html_string(&self) -> String {
|
||||||
format!("<img src={path} />", path = self.path,)
|
format!(
|
||||||
|
"<img src={path} {attrs} />",
|
||||||
|
path = self.path,
|
||||||
|
attrs = self.attributes.to_string()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,22 +37,27 @@ pub fn auth(_message: Option<String>) -> build_html::HtmlPage {
|
||||||
|
|
||||||
pub fn gallery(handles: Vec<Result<FileHandle, ReadFileError>>) -> build_html::HtmlPage {
|
pub fn gallery(handles: Vec<Result<FileHandle, ReadFileError>>) -> build_html::HtmlPage {
|
||||||
let mut page = build_html::HtmlPage::new()
|
let mut page = build_html::HtmlPage::new()
|
||||||
.with_title("Admin list of files")
|
.with_title("Gallery")
|
||||||
.with_stylesheet("/css")
|
.with_stylesheet("/css")
|
||||||
.with_header(1, "Admin list of files")
|
.with_container(
|
||||||
.with_html(
|
Container::new(ContainerType::Div)
|
||||||
Form::new()
|
.with_attributes([("class", "gallery-page")])
|
||||||
.with_path("/upload")
|
.with_header(1, "Gallery")
|
||||||
.with_method("post")
|
.with_html(
|
||||||
.with_encoding("multipart/form-data")
|
Form::new()
|
||||||
.with_container(
|
.with_path("/upload")
|
||||||
Container::new(ContainerType::Div)
|
.with_method("post")
|
||||||
.with_html(Input::new("file", "file").with_id("for-selector-input"))
|
.with_encoding("multipart/form-data")
|
||||||
.with_html(Label::new("for-selector-input", "Select a file")),
|
.with_container(
|
||||||
)
|
Container::new(ContainerType::Div)
|
||||||
.with_html(Button::new("Upload file").with_type("submit")),
|
.with_html(Input::new("file", "file").with_id("for-selector-input"))
|
||||||
|
.with_html(Label::new("for-selector-input", "Select a file")),
|
||||||
|
)
|
||||||
|
.with_html(Button::new("Upload file").with_type("submit")),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut gallery = Container::new(ContainerType::Div).with_attributes([("class", "gallery")]);
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
let container = match handle {
|
let container = match handle {
|
||||||
Ok(ref handle) => thumbnail(&handle.id).with_html(
|
Ok(ref handle) => thumbnail(&handle.id).with_html(
|
||||||
|
@ -67,19 +72,21 @@ pub fn gallery(handles: Vec<Result<FileHandle, ReadFileError>>) -> build_html::H
|
||||||
.with_attributes(vec![("class", "file")])
|
.with_attributes(vec![("class", "file")])
|
||||||
.with_paragraph(format!("{:?}", err)),
|
.with_paragraph(format!("{:?}", err)),
|
||||||
};
|
};
|
||||||
page.add_container(container)
|
gallery.add_container(container);
|
||||||
}
|
}
|
||||||
|
page.add_container(gallery);
|
||||||
page
|
page
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn thumbnail(id: &FileId) -> Container {
|
pub fn thumbnail(id: &FileId) -> Container {
|
||||||
let mut container = Container::new(ContainerType::Div).with_attributes(vec![("class", "file")]);
|
let mut container =
|
||||||
let tn = Container::new(ContainerType::Div)
|
Container::new(ContainerType::Div).with_attributes(vec![("class", "thumbnail")]);
|
||||||
.with_attributes(vec![("class", "thumbnail")])
|
let tn = Container::new(ContainerType::Div).with_link(
|
||||||
.with_link(
|
format!("/{}", **id),
|
||||||
format!("/{}", **id),
|
Image::new(&format!("{}/tn", **id))
|
||||||
Image::new(&format!("{}/tn", **id)).to_html_string(),
|
.with_attributes([("class", "thumbnail__image")])
|
||||||
);
|
.to_html_string(),
|
||||||
|
);
|
||||||
container.add_html(tn);
|
container.add_html(tn);
|
||||||
container
|
container
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
--main-bg-color: #e5f0fc;
|
--main-bg-color: #e5f0fc;
|
||||||
--fg-color: #449dfc;
|
--fg-color: #449dfc;
|
||||||
|
|
||||||
--px-4: 4px;
|
--space-small: 4px;
|
||||||
--px-8: 8px;
|
--space-medium: 8px;
|
||||||
--px-12: 12px;
|
--space-large: 12px;
|
||||||
|
|
||||||
--hover-low: 4px 4px 4px gray;
|
--hover-low: 4px 4px 4px gray;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ body {
|
||||||
|
|
||||||
.authentication-page {
|
.authentication-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
@ -29,35 +30,46 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.authentication-form__label {
|
.authentication-form__label {
|
||||||
margin: var(--px-4);
|
margin: var(--space-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
.authentication-form__input {
|
.authentication-form__input {
|
||||||
margin: var(--px-4);
|
margin: var(--space-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
.files {
|
.gallery-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file {
|
.gallery {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 1em;
|
|
||||||
border: 1px solid #449dfc;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
border: 1px solid black;
|
||||||
|
box-shadow: var(--hover-low);
|
||||||
|
max-width: 300px;
|
||||||
|
margin: var(--space-large);
|
||||||
|
padding: var(--space-medium);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
.thumbnail {
|
.thumbnail {
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
img {
|
.thumbnail__image {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
[type="submit"] {
|
[type="submit"] {
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
|
@ -69,8 +81,10 @@ img {
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/* https://benmarshall.me/styling-file-inputs/ */
|
/* https://benmarshall.me/styling-file-inputs/ */
|
||||||
|
/*
|
||||||
[type="file"] {
|
[type="file"] {
|
||||||
border: 0;
|
border: 0;
|
||||||
clip: rect(0, 0, 0, 0);
|
clip: rect(0, 0, 0, 0);
|
||||||
|
@ -102,6 +116,7 @@ img {
|
||||||
outline: 1px dotted #000;
|
outline: 1px dotted #000;
|
||||||
outline: -webkit-focus-ring-color auto 5px;
|
outline: -webkit-focus-ring-color auto 5px;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@media screen and (max-width: 980px) { /* This is the screen width of a OnePlus 5t
|
@media screen and (max-width: 980px) { /* This is the screen width of a OnePlus 5t
|
||||||
|
|
Loading…
Reference in New Issue