Initial commit. UI localisation works.

This commit is contained in:
2023-08-18 12:22:23 +02:00
commit 9f17393310
100 changed files with 39402 additions and 0 deletions
+228
View File
@@ -0,0 +1,228 @@
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
use crate::home_page::HomePage;
use crate::locales::trl;
#[component]
pub fn App(cx: Scope) -> impl IntoView {
// Provides context that manages stylesheets, titles, meta tags, etc.
provide_meta_context(cx);
//provide_context(cx, DialogOpener::new(cx));
view! {
cx,
<Html
lang="cz"
dir="ltr"
attributes=AdditionalAttributes::from(vec![
("data-theme", "theme-default"),
("class", "light-style layout-menu-fixed"),
("data-template", "vertical-menu-template-free"),
("data-assets-path", "/")])
/>
<Meta charset="utf-8"/>
<Meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"/>
//<!-- Fonts -->
<Link rel="preconnect" href="https://fonts.googleapis.com" />
<Link rel="preconnect" href="https://fonts.gstatic.com" />
<Link
href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap"
rel="stylesheet"
/>
//<!-- Icons. Uncomment required icon fonts -->
<Link rel="stylesheet" href="/vendor/fonts/boxicons.css" />
//<!-- Core CSS -->
<Link rel="stylesheet" href="/vendor/css/core.css" />
<Link rel="stylesheet" href="/vendor/css/theme-default.css" />
<Link rel="stylesheet" href="/css/demo.css" />
//<!-- Vendors CSS -->
<Link rel="stylesheet" href="/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" />
<Body class="testik"/>
<div class="layout-wrapper layout-content-navbar">
<div class="layout-container">
//<!-- Menu -->
<aside id="layout-menu" class="layout-menu menu-vertical menu bg-menu-theme">
<div class="app-brand demo">
<a href="javascript:void(0);" class="layout-menu-toggle menu-link text-large ms-auto d-block d-xl-none">
<i class="bx bx-chevron-left bx-sm align-middle"></i>
</a>
</div>
<div class="menu-inner-shadow"></div>
<ul class="menu-inner py-1">
//<!-- Dashboard -->
<li class="menu-item">
<a href="/" class="menu-link">
<i class="menu-icon tf-icons bx bx-home-circle"></i>
<div data-i18n="Analytics">{trl(cx, "Dashboard")}</div>
</a>
</li>
<li class="menu-item">
<a href="/" class="menu-link">
<i class="menu-icon tf-icons bx bx-time"></i>
<div data-i18n="Analytics">"Opening hours"</div>
</a>
</li>
<li class="menu-item">
<a href="/" class="menu-link">
<i class="menu-icon tf-icons bx bx-layer"></i>
<div data-i18n="Analytics">"Places"</div>
</a>
</li>
<li class="menu-item">
<a href="/" class="menu-link">
<i class="menu-icon tf-icons bx bx-info-circle"></i>
<div data-i18n="Analytics">"About"</div>
</a>
</li>
</ul>
</aside>
//<!-- Layout container -->
<div class="layout-page">
//<!-- Navbar -->
<nav
class="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
id="layout-navbar">
<div class="layout-menu-toggle navbar-nav align-items-xl-center me-3 me-xl-0 d-xl-none">
<a class="nav-item nav-link px-0 me-xl-4" href="javascript:void(0)">
<i class="bx bx-menu bx-sm"></i>
</a>
</div>
<div class="navbar-nav-right d-flex align-items-center" id="navbar-collapse">
//<!-- Search -->
<div class="navbar-nav align-items-center">
<div class="nav-item d-flex align-items-center">
<i class="bx bx-search fs-4 lh-0"></i>
<input
type="text"
class="form-control border-0 shadow-none"
placeholder={trl(cx, "Search...")}
aria-label={trl(cx, "Search...")}
/>
</div>
</div>
//<!-- /Search -->
<ul class="navbar-nav flex-row align-items-center ms-auto">
<li class="nav-item navbar-dropdown dropdown-user dropdown">
<a class="nav-link dropdown-toggle hide-arrow" href="#" data-bs-toggle="dropdown">
<i class="bx bx-cog fs-3 lh-0"></i>
</a>
</li>
//<!-- User -->
<li class="nav-item navbar-dropdown dropdown-user dropdown">
<a class="nav-link dropdown-toggle hide-arrow" href="#" data-bs-toggle="dropdown">
//<div class="avatar avatar-online">
// <img src="/img/avatars/1.png" alt class="w-px-40 h-auto rounded-circle" />
//</div>
<i class="bx bx-user fs-3 lh-0"></i>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="#">
<div class="d-flex">
<div class="flex-shrink-0 me-3">
<div class="avatar avatar-online">
<img src="/img/avatars/1.png" alt class="w-px-40 h-auto rounded-circle" />
</div>
</div>
<div class="flex-grow-1">
<span class="fw-semibold d-block">"John Doe"</span>
<small class="text-muted">"Admin"</small>
</div>
</div>
</a>
</li>
<li>
<div class="dropdown-divider"></div>
</li>
<li>
<a class="dropdown-item" href="#">
<i class="bx bx-user me-2"></i>
<span class="align-middle">"My Profile"</span>
</a>
</li>
<li>
<a class="dropdown-item" href="#">
<i class="bx bx-cog me-2"></i>
<span class="align-middle">"Settings"</span>
</a>
</li>
<li>
<div class="dropdown-divider"></div>
</li>
<li>
<a class="dropdown-item" href="auth-login-basic.html">
<i class="bx bx-power-off me-2"></i>
<span class="align-middle">"Log Out"</span>
</a>
</li>
</ul>
</li>
//<!--/ User -->
</ul>
</div>
</nav>
//<!-- Content wrapper -->
<div class="content-wrapper">
//<!-- Content -->
<div class="container-xxl flex-grow-1 container-p-y">
<Router>
<main>
<Routes>
<Route path="" view=|cx| view! { cx, <HomePage/> }/>
</Routes>
</main>
</Router>
</div>
</div>
</div>
</div>
</div>
/*<Html lang="cz"/>
// injects a stylesheet into the document <head>
// id=leptos means cargo-leptos will hot-reload this stylesheet
<Stylesheet id="leptos" href="/pkg/leptos_start.css"/>
<Stylesheet href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"/>
// sets the document title
<Title text="Welcome to Leptos"/>
<div class="topbar">
<img src="/logo.png" width=40 height=40/><h1>"Rezervator admin"</h1>
</div>
<div class="main">
<div class="sidebar">
<p></p>
<a class="active" href="#home">"Dashboard"</a>
<a href="#news">"Opening hours"</a>
<a href="#contact">"Places"</a>
<a href="#about">"About"</a>
</div>
// content for this welcome page
<div class="content">
<Router>
<main>
<Routes>
<Route path="" view=|cx| view! { cx, <HomePage/> }/>
</Routes>
</main>
</Router>
</div>
</div>*/
}
}
+80
View File
@@ -0,0 +1,80 @@
use leptos::*;
use crate::modal_box::{DialogOpener, ModalDialog, ModalBody, ModalFooter};
use crate::server_fn::*;
use crate::locales::trl;
/// Renders the home page of your application.
#[component]
pub fn HomePage(cx: Scope) -> impl IntoView {
// Creates a reactive value to update the button
let (count, set_count) = create_signal(cx, 0);
let on_click = move |_| set_count.update(|count| *count += 1);
let dialog = DialogOpener::new(cx);
//let (dialog, set_dialog) = create_signal(cx, false);
//let on_dialog = move |_| dialog.set_visible.update(|dialog| {*dialog = true});
//let pok = use_context::<Request>(cx);
//log!("{:?}", pok);
view! { cx,
<ModalDialog opener={dialog} title="Titulek".to_string()>
<ModalBody>
<div class="row">
<div class="col mb-3">
<label for="nameWithTitle" class="form-label">"Name"</label>
<input
type="text"
id="nameWithTitle"
class="form-control"
placeholder="Enter Name"
/>
</div>
</div>
<div class="row g-2">
<div class="col mb-0">
<label for="emailWithTitle" class="form-label">"Email"</label>
<input
type="text"
id="emailWithTitle"
class="form-control"
placeholder="xxxx@xxx.xx"
/>
</div>
<div class="col mb-0">
<label for="dobWithTitle" class="form-label">"DOB"</label>
<input
type="text"
id="dobWithTitle"
class="form-control"
placeholder="DD / MM / YY"
/>
</div>
</div>
</ModalBody>
<ModalFooter>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal" on:click=move |_| dialog.hide()>
"Close"
</button>
<button type="button" class="btn btn-primary">"Save changes"</button>
</ModalFooter>
</ModalDialog>
<h1>"Welcome to Leptos!"</h1>
<button on:click=on_click>"Click Me: " {count}</button>
<button on:click=move |_| dialog.show()>"Dialog"</button>
<button on:click=move |_| {
spawn_local(async move {
set_session(cx).await;
});
}>"Session"</button>
<button on:click=move |_| {
spawn_local(async move {
get_session(cx).await;
});
}>"Session get"</button>
<p>{trl(cx, "testik!")}</p>
}
}
+26
View File
@@ -0,0 +1,26 @@
pub mod app;
pub mod modal_box;
pub mod home_page;
pub mod server_fn;
pub mod locales;
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "hydrate")] {
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
pub fn hydrate() {
use app::*;
use leptos::*;
console_error_panic_hook::set_once();
leptos::mount_to_body(move |cx| {
view! { cx, <App/> }
});
}
}
}
+52
View File
@@ -0,0 +1,52 @@
use std::collections::HashMap;
use lazy_static::lazy_static;
use leptos::*;
lazy_static! {
static ref LANGUAGES: HashMap<&'static str, HashMap<&'static str, &'static str>> = {
let m = HashMap::from([
("cs", HashMap::from( [
("Dashboard", "Přehled"),
("Settings", "Nastavení"),
("Search...", "Najít...")
])),
("sk", HashMap::from( [
("Dashboard", "Prehlad"),
("Settings", "Nastavenie"),
("Search...", "Najsť...")
]))
]);
m
};
}
fn get_locales(cx: Scope) -> Vec<Option<String>> {
let (loc, set_loc) = create_signal(cx, Vec::new());
create_effect(cx, move |_| {
let js_locales = window().navigator().languages();
set_loc.update(|l| *l = js_locales.into_iter().map(|val| val.as_string()).collect::<Vec<_>>());
});
loc.get_untracked()
}
pub fn get_dictionary(cx: Scope) -> Option<&'static HashMap<&'static str, &'static str>> {
let locs = get_locales(cx);
for loc in locs {
if let Some(key) = loc {
if let Some(k) = key.split("-").collect::<Vec<_>>().get(0) {
if *k == "en" {
return None;
}
if !LANGUAGES.contains_key(*k) {
continue;
}
return LANGUAGES.get(*k);
}
}
}
None
}
+15
View File
@@ -0,0 +1,15 @@
use leptos::*;
use crate::locales::catalogues::get_dictionary;
mod catalogues;
pub fn trl(cx: Scope, phrase: &'static str) -> impl Fn() -> &'static str {
let mut translated = phrase;
if let Some(dict) = get_dictionary(cx) {
if let Some(p) = dict.get(phrase) {
translated = *p;
}
}
|| { translated }
}
+70
View File
@@ -0,0 +1,70 @@
#[cfg(feature = "ssr")]
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_files::Files;
use actix_web::*;
use leptos::*;
use leptos_actix::{generate_route_list, LeptosRoutes};
use rezervator::app::*;
//use rezervator::server_fn::*;
use actix_session::storage::CookieSessionStore;
use actix_session::SessionMiddleware;
use actix_web::cookie::Key;
let conf = get_configuration(None).await.unwrap();
let addr = conf.leptos_options.site_addr;
// Generate the list of routes in your Leptos App
let routes = generate_route_list(|cx| view! { cx, <App/> });
let key = Key::generate();
//SetSession::register();
//GetSession::register();
HttpServer::new(move || {
let leptos_options = &conf.leptos_options;
let site_root = &leptos_options.site_root;
App::new()
.wrap(SessionMiddleware::new(
CookieSessionStore::default(),
key.clone()
))
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
.leptos_routes(
leptos_options.to_owned(),
routes.to_owned(),
|cx| view! { cx, <App/> },
)
.service(Files::new("/", site_root))
//.wrap(middleware::Compress::default())
})
.bind(&addr)?
.run()
.await
}
#[cfg(not(any(feature = "ssr", feature = "csr")))]
pub fn main() {
// no client-side main function
// unless we want this to work with e.g., Trunk for pure client-side testing
// see lib.rs for hydration function instead
// see optional feature `ssg` instead
}
#[cfg(all(not(feature = "ssr"), feature = "csr"))]
pub fn main() {
// a client-side main function is required for using `trunk serve`
// prefer using `cargo leptos serve` instead
// to run: `trunk serve --open --features ssg`
use leptos::*;
use leptos_start::app::*;
use wasm_bindgen::prelude::wasm_bindgen;
console_error_panic_hook::set_once();
leptos::mount_to_body(move |cx| {
// note: for testing it may be preferrable to replace this with a
// more specific component, although leptos_router should still work
view! {cx, <App/> }
});
}
+78
View File
@@ -0,0 +1,78 @@
use std::rc::Rc;
use leptos::*;
#[derive(Copy, Clone)]
pub struct DialogOpener {
visible: ReadSignal<bool>,
set_visible: WriteSignal<bool>,
}
impl DialogOpener {
pub fn new(cx: Scope) -> Self {
let (visible, set_visible) = create_signal(cx, false);
DialogOpener {
visible,
set_visible,
}
}
pub fn visible(&self) -> bool {
self.visible.get()
}
pub fn show(&self) {
self.set_visible.update(|state| { *state = true});
}
pub fn hide(&self) {
self.set_visible.update(|state| { *state = false});
}
}
#[component]
pub fn ModalDialog(
cx: Scope,
opener: DialogOpener,
title: String,
children: Children,
) -> impl IntoView {
view! {cx,
<div class={ move || if opener.visible() {"modal fade show"} else {"modal fade"}}
style={ move || if opener.visible() {"display: block;"} else {""}}
id="modalCenter" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalCenterTitle">{title}</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
on:click=move |_| opener.hide()/>
</div>
{children(cx)}
</div>
</div>
</div>
}
}
#[component]
pub fn ModalBody(cx: Scope, children: Children) -> impl IntoView {
view! {cx,
<div class="modal-body">
{children(cx)}
</div>
}
}
#[component]
pub fn ModalFooter(cx: Scope, children: Children) -> impl IntoView {
view! {cx,
<div class="modal-footer">
{children(cx)}
</div>
}
}
+29
View File
@@ -0,0 +1,29 @@
use leptos::*;
#[server(SetSession, "/api", "Url", "set_session")]
pub async fn set_session(cx: Scope) -> Result<(), ServerFnError> {
use leptos_actix::extract;
use actix_session::*;
extract(cx, |session: Session| async move {
leptos::log!("extract");
let pok = session.insert("user", "uzivatel");
log!("{pok:?}");
}).await
//Ok(())
}
#[server(GetSession, "/api")]
pub async fn get_session(cx: Scope) -> Result<(), ServerFnError> {
use leptos_actix::extract;
use actix_session::*;
extract(cx, |session: Session| async move {
leptos::log!("extract");
let pok = session.get::<String>("user");
log!("{pok:?}");
}).await
//Ok(())
}