Initial commit. UI localisation works.
This commit is contained in:
+228
@@ -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>*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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/> }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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/> }
|
||||
});
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user