Add download qrcode to table
This commit is contained in:
parent
6b0daecd31
commit
5886272585
@ -1,6 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use fluent::{FluentArgs, FluentBundle, FluentResource};
|
use fluent::{FluentArgs, FluentBundle, FluentResource};
|
||||||
|
use seed::log;
|
||||||
use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
|
use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
|
||||||
use unic_langid::LanguageIdentifier;
|
use unic_langid::LanguageIdentifier;
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ impl I18n {
|
|||||||
|
|
||||||
/// Get a localized string. Optionally with parameters provided in `args`.
|
/// Get a localized string. Optionally with parameters provided in `args`.
|
||||||
pub fn translate(&self, key: impl AsRef<str>, args: Option<&FluentArgs>) -> String {
|
pub fn translate(&self, key: impl AsRef<str>, args: Option<&FluentArgs>) -> String {
|
||||||
|
log!(key.as_ref());
|
||||||
let msg = self
|
let msg = self
|
||||||
.ftl_bundle
|
.ftl_bundle
|
||||||
.get_message(key.as_ref())
|
.get_message(key.as_ref())
|
||||||
|
@ -4,6 +4,7 @@ pub mod pages;
|
|||||||
|
|
||||||
use pages::list_links;
|
use pages::list_links;
|
||||||
use pages::list_users;
|
use pages::list_users;
|
||||||
|
use seed::window;
|
||||||
use seed::{attrs, button, div, input, label, log, prelude::*, App, Url, C};
|
use seed::{attrs, button, div, input, label, log, prelude::*, App, Url, C};
|
||||||
use shared::apirequests::users::LoginUser;
|
use shared::apirequests::users::LoginUser;
|
||||||
use shared::datatypes::{Loadable, User};
|
use shared::datatypes::{Loadable, User};
|
||||||
@ -18,14 +19,11 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
|||||||
orders.subscribe(Msg::UrlChanged);
|
orders.subscribe(Msg::UrlChanged);
|
||||||
orders.send_msg(Msg::GetLoggedUser);
|
orders.send_msg(Msg::GetLoggedUser);
|
||||||
|
|
||||||
log!(&url);
|
|
||||||
|
|
||||||
let lang = I18n::new(Lang::EnUS);
|
let lang = I18n::new(Lang::EnUS);
|
||||||
|
|
||||||
Model {
|
Model {
|
||||||
index: 0,
|
index: 0,
|
||||||
base_url: Url::new().add_path_part("app"),
|
location: Location::new(url.clone()),
|
||||||
current_url: url.clone(),
|
|
||||||
page: Page::init(url, orders, lang.clone()),
|
page: Page::init(url, orders, lang.clone()),
|
||||||
i18n: lang,
|
i18n: lang,
|
||||||
user: Loadable::Data(None),
|
user: Loadable::Data(None),
|
||||||
@ -41,8 +39,7 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Model {
|
struct Model {
|
||||||
index: usize,
|
index: usize,
|
||||||
base_url: Url,
|
location: Location,
|
||||||
current_url: Url,
|
|
||||||
page: Page,
|
page: Page,
|
||||||
i18n: i18n::I18n,
|
i18n: i18n::I18n,
|
||||||
user: Loadable<User>,
|
user: Loadable<User>,
|
||||||
@ -56,6 +53,32 @@ struct LoginForm {
|
|||||||
password: ElRef<web_sys::HtmlInputElement>,
|
password: ElRef<web_sys::HtmlInputElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Location {
|
||||||
|
host: String,
|
||||||
|
base_url: Url,
|
||||||
|
current_url: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Location {
|
||||||
|
fn new(url: Url) -> Self {
|
||||||
|
let host = get_host();
|
||||||
|
Self {
|
||||||
|
host,
|
||||||
|
base_url: Url::new().add_path_part("app"),
|
||||||
|
current_url: url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_host() -> String {
|
||||||
|
window()
|
||||||
|
.location()
|
||||||
|
.host()
|
||||||
|
.expect("Failed to extract the host of the url")
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Page {
|
enum Page {
|
||||||
Home(pages::list_links::Model),
|
Home(pages::list_links::Model),
|
||||||
@ -65,6 +88,7 @@ enum Page {
|
|||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
fn init(mut url: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Self {
|
fn init(mut url: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Self {
|
||||||
|
log!(&url);
|
||||||
url.next_path_part();
|
url.next_path_part();
|
||||||
let result = match url.next_path_part() {
|
let result = match url.next_path_part() {
|
||||||
None | Some("list_links") => Self::Home(pages::list_links::init(
|
None | Some("list_links") => Self::Home(pages::list_links::init(
|
||||||
@ -140,7 +164,11 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
Msg::UserReceived(user) => {
|
Msg::UserReceived(user) => {
|
||||||
model.user = Loadable::Data(Some(user));
|
model.user = Loadable::Data(Some(user));
|
||||||
model.page = Page::init(model.current_url.clone(), orders, model.i18n.clone());
|
model.page = Page::init(
|
||||||
|
model.location.current_url.clone(),
|
||||||
|
orders,
|
||||||
|
model.i18n.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Msg::NotAuthenticated => {
|
Msg::NotAuthenticated => {
|
||||||
if model.user.is_some() {
|
if model.user.is_some() {
|
||||||
@ -252,8 +280,8 @@ fn view(model: &Model) -> Node<Msg> {
|
|||||||
C!["page"],
|
C!["page"],
|
||||||
match model.user {
|
match model.user {
|
||||||
Loadable::Data(Some(ref user)) => div![
|
Loadable::Data(Some(ref user)) => div![
|
||||||
navigation::navigation(&model.i18n, &model.base_url, user),
|
navigation::navigation(&model.i18n, &model.location.base_url, user),
|
||||||
view_content(&model.page, &model.base_url)
|
view_content(&model.page, &model.location.base_url)
|
||||||
],
|
],
|
||||||
Loadable::Data(None) => view_login(&model.i18n, model),
|
Loadable::Data(None) => view_login(&model.i18n, model),
|
||||||
Loadable::Loading => div![C!("lds-ellipsis"), div!(), div!(), div!(), div!()],
|
Loadable::Loading => div![C!("lds-ellipsis"), div!(), div!(), div!(), div!()],
|
||||||
|
@ -17,7 +17,7 @@ use shared::{
|
|||||||
datatypes::FullLink,
|
datatypes::FullLink,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{i18n::I18n, unwrap_or_return, unwrap_or_send};
|
use crate::{get_host, i18n::I18n, unwrap_or_return, unwrap_or_send};
|
||||||
|
|
||||||
/// Setup the page
|
/// Setup the page
|
||||||
pub fn init(mut url: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Model {
|
pub fn init(mut url: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Model {
|
||||||
@ -543,6 +543,15 @@ fn view_link(l: &FullLink) -> Node<Msg> {
|
|||||||
&l.clicks.number
|
&l.clicks.number
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
td![
|
||||||
|
C!["table_qr"],
|
||||||
|
a![
|
||||||
|
attrs![At::Href => format!["http://localhost:8080/admin/download/png/{}", &l.link.code], At::Download => true.as_at_value()],
|
||||||
|
raw!(&generate_qr_from_code(&l.link.code))
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
td![img![
|
td![img![
|
||||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::MayDeleteSelected(link5))),
|
ev(Ev::Click, |_| Msg::Edit(EditMsg::MayDeleteSelected(link5))),
|
||||||
@ -602,7 +611,7 @@ fn edit_or_create_link<F: Fn(&str) -> String>(l: &RefCell<LinkDelta>, t: F) -> N
|
|||||||
],
|
],
|
||||||
tr![
|
tr![
|
||||||
th![t("qr-code")],
|
th![t("qr-code")],
|
||||||
td![raw!(&generate_qr_code(&format!("http://{}", &link.code)))]
|
td![raw!(&generate_qr_from_code(&link.code))]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
a![
|
a![
|
||||||
@ -616,8 +625,12 @@ fn edit_or_create_link<F: Fn(&str) -> String>(l: &RefCell<LinkDelta>, t: F) -> N
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_qr_code(link: &str) -> String {
|
fn generate_qr_from_code(code: &str) -> String {
|
||||||
if let Ok(qr) = QrCode::with_error_correction_level(&link, qrcode::EcLevel::L) {
|
generate_qr_from_link(&format!("https://{}/{}", get_host(), code))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_qr_from_link(url: &str) -> String {
|
||||||
|
if let Ok(qr) = QrCode::with_error_correction_level(&url, qrcode::EcLevel::L) {
|
||||||
let svg = qr
|
let svg = qr
|
||||||
.render()
|
.render()
|
||||||
.min_dimensions(100, 100)
|
.min_dimensions(100, 100)
|
||||||
|
@ -17,6 +17,7 @@ create-link = Create link
|
|||||||
link-description = Description
|
link-description = Description
|
||||||
link-target = Link target
|
link-target = Link target
|
||||||
link-code = Link code
|
link-code = Link code
|
||||||
|
qr-code = QR-code
|
||||||
shortlink = Shortlink
|
shortlink = Shortlink
|
||||||
search-placeholder = Filter according to...
|
search-placeholder = Filter according to...
|
||||||
really-delete = Do you really want to delete {$code}?
|
really-delete = Do you really want to delete {$code}?
|
||||||
|
@ -357,6 +357,7 @@ pub async fn webservice(
|
|||||||
"/create_link/",
|
"/create_link/",
|
||||||
web::post().to(views::process_create_link_json),
|
web::post().to(views::process_create_link_json),
|
||||||
)
|
)
|
||||||
|
.route("/get_qr_code/", web::post().to(views::get_qr_code_json))
|
||||||
.route(
|
.route(
|
||||||
"/edit_link/",
|
"/edit_link/",
|
||||||
web::post().to(views::process_update_link_json),
|
web::post().to(views::process_update_link_json),
|
||||||
|
@ -16,7 +16,7 @@ use qrcode::{render::svg, QrCode};
|
|||||||
use queries::{authenticate, Role};
|
use queries::{authenticate, Role};
|
||||||
use shared::apirequests::{
|
use shared::apirequests::{
|
||||||
general::{Message, Status},
|
general::{Message, Status},
|
||||||
links::{LinkDelta, LinkRequestForm},
|
links::{LinkDelta, LinkRequestForm, QrCodeRequest, SvgQrCodeResponse},
|
||||||
users::{LoginUser, UserDelta, UserRequestForm},
|
users::{LoginUser, UserDelta, UserRequestForm},
|
||||||
};
|
};
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
@ -130,7 +130,7 @@ pub async fn index_json(
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to access database: {:?}", e);
|
error!("Failed to access database: {:?}", e);
|
||||||
warn!("Not logged in - redirecting to login page");
|
warn!("Not logged in - redirecting to login page");
|
||||||
Ok(redirect_builder("/admin/login/"))
|
Ok(HttpResponse::Unauthorized().body("Failed"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,6 +188,31 @@ pub async fn view_link_empty(
|
|||||||
view_link(tera, config, id, web::Path::from("".to_owned())).await
|
view_link(tera, config, id, web::Path::from("".to_owned())).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_qr_code_json(
|
||||||
|
config: web::Data<crate::ServerConfig>,
|
||||||
|
qr_request: web::Json<QrCodeRequest>,
|
||||||
|
id: Identity,
|
||||||
|
) -> Result<HttpResponse, ServerError> {
|
||||||
|
if let Ok(link) = queries::get_link(&id, &qr_request.link_id, &config).await {
|
||||||
|
let host = config.public_url.to_string();
|
||||||
|
let qr = QrCode::with_error_correction_level(
|
||||||
|
&format!("http://{}/{}", &host, &link.item.code),
|
||||||
|
qrcode::EcLevel::L,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let svg = qr
|
||||||
|
.render()
|
||||||
|
.min_dimensions(100, 100)
|
||||||
|
.dark_color(svg::Color("#000000"))
|
||||||
|
.light_color(svg::Color("#ffffff"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json2(&SvgQrCodeResponse { svg }))
|
||||||
|
} else {
|
||||||
|
Ok(redirect_builder("/admin/login/"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip(id, tera))]
|
#[instrument(skip(id, tera))]
|
||||||
pub async fn view_link(
|
pub async fn view_link(
|
||||||
tera: web::Data<Tera>,
|
tera: web::Data<Tera>,
|
||||||
|
@ -40,6 +40,7 @@ div.login div {
|
|||||||
height: 60px;
|
height: 60px;
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.login input {
|
div.login input {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -63,6 +64,10 @@ td {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td.table_qr svg {
|
||||||
|
max-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
table tr:nth-child(even) {
|
table tr:nth-child(even) {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
@ -73,3 +73,20 @@ pub enum LinkOverviewColumns {
|
|||||||
Author,
|
Author,
|
||||||
Statistics,
|
Statistics,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)]
|
||||||
|
pub struct QrCodeRequest {
|
||||||
|
pub link_id: String,
|
||||||
|
pub format: QrCodeFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)]
|
||||||
|
pub struct SvgQrCodeResponse {
|
||||||
|
pub svg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)]
|
||||||
|
pub enum QrCodeFormat {
|
||||||
|
Svg,
|
||||||
|
Png,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user