Implémentation "HATEOAS" de l'interface pour HTMX et update des URLs qui fonctionne ! #57
6
crates/app/askama.toml
Normal file
6
crates/app/askama.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[general]
|
||||||
|
# Directories to search for templates, relative to the crate root.
|
||||||
|
dirs = [
|
||||||
|
"src/pages",
|
||||||
|
"src/components",
|
||||||
|
]
|
@ -1,34 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}Pharma Libre{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<div class="py-10">
|
|
||||||
<header>
|
|
||||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
||||||
<h1
|
|
||||||
id="page-title"
|
|
||||||
class="text-3xl font-bold leading-tight tracking-tight text-gray-900"
|
|
||||||
>
|
|
||||||
{% include "skeletons/page-title.html" %}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<div
|
|
||||||
id="main-container"
|
|
||||||
class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"
|
|
||||||
>
|
|
||||||
<!-- Your content -->
|
|
||||||
<div
|
|
||||||
hx-get="/pages/home"
|
|
||||||
hx-target="this"
|
|
||||||
hx-trigger="load"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
>
|
|
||||||
{% include "skeletons/card.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -9,14 +9,10 @@
|
|||||||
<link href="/assets/css/flowbite@2.5.1.min.css" rel="stylesheet" />
|
<link href="/assets/css/flowbite@2.5.1.min.css" rel="stylesheet" />
|
||||||
<script src="/assets/js/flowbite@2.5.1.min.js"></script>
|
<script src="/assets/js/flowbite@2.5.1.min.js"></script>
|
||||||
|
|
||||||
{% block head %}{% endblock %}
|
{% block headx %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body class="h-full">
|
<body class="h-full">
|
||||||
<div class="min-h-full">
|
<div class="min-h-full">
|
||||||
{% block nav %}
|
|
||||||
{% include "layout/nav.html" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
14
crates/app/src/components/navbar/menu-item.html
Normal file
14
crates/app/src/components/navbar/menu-item.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{% set selected = item.id == current %}
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="{{ item.href }}"
|
||||||
|
{% if selected -%}
|
||||||
|
class="block py-2 px-3 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 md:dark:text-blue-500"
|
||||||
|
aria-current="page"
|
||||||
|
{% else -%}
|
||||||
|
class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||||
|
{% endif -%}
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a>
|
||||||
|
</li>
|
50
crates/app/src/components/navbar/navbar.html
Normal file
50
crates/app/src/components/navbar/navbar.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{% macro navbar(current) %}
|
||||||
|
|
||||||
|
{% let items=crate::menu::get_menu_items() %}
|
||||||
|
|
||||||
|
<nav class="bg-white border-gray-200 dark:bg-gray-900">
|
||||||
|
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
|
||||||
|
<a href="/" class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||||
|
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8" alt="Flowbite Logo" />
|
||||||
|
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Krys4lide</span>
|
||||||
|
</a>
|
||||||
|
<div class="flex items-center md:order-2 space-x-3 md:space-x-0 rtl:space-x-reverse">
|
||||||
|
<button type="button" class="flex text-sm bg-gray-800 rounded-full md:me-0 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600" id="user-menu-button" aria-expanded="false" data-dropdown-toggle="user-dropdown" data-dropdown-placement="bottom">
|
||||||
|
<span class="sr-only">Open user menu</span>
|
||||||
|
<img class="w-8 h-8 rounded-full" src="https://flowbite.com/docs/images/people/profile-picture-3.jpg" alt="user photo">
|
||||||
|
</button>
|
||||||
|
<!-- Dropdown menu -->
|
||||||
|
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600" id="user-dropdown">
|
||||||
|
<div class="px-4 py-3">
|
||||||
|
<span class="block text-sm text-gray-900 dark:text-white">Bonnie Green</span>
|
||||||
|
<span class="block text-sm text-gray-500 truncate dark:text-gray-400">name@flowbite.com</span>
|
||||||
|
</div>
|
||||||
|
<ul class="py-2" aria-labelledby="user-menu-button">
|
||||||
|
<li>
|
||||||
|
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Profile</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Settings</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Sign out</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<button data-collapse-toggle="navbar-user" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-user" aria-expanded="false">
|
||||||
|
<span class="sr-only">Open main menu</span>
|
||||||
|
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="items-center justify-between hidden w-full md:flex md:w-auto md:order-1" id="navbar-user">
|
||||||
|
<ul class="flex flex-col font-medium p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:space-x-8 rtl:space-x-reverse md:flex-row md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
|
||||||
|
{% for item in items %}
|
||||||
|
{% include "navbar/menu-item.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
{% endmacro %}
|
@ -4,6 +4,9 @@ use askama_axum::Template;
|
|||||||
use axum::http::{StatusCode, Uri};
|
use axum::http::{StatusCode, Uri};
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
|
mod menu;
|
||||||
|
mod pages;
|
||||||
|
|
||||||
async fn fallback(uri: Uri) -> (StatusCode, String) {
|
async fn fallback(uri: Uri) -> (StatusCode, String) {
|
||||||
(StatusCode::NOT_FOUND, format!("No route for {uri}"))
|
(StatusCode::NOT_FOUND, format!("No route for {uri}"))
|
||||||
}
|
}
|
||||||
@ -20,7 +23,6 @@ pub fn get_router(assets_path: &Path) -> axum::Router {
|
|||||||
axum::Router::new()
|
axum::Router::new()
|
||||||
.nest_service("/assets", ServeDir::new(assets_path))
|
.nest_service("/assets", ServeDir::new(assets_path))
|
||||||
.route("/", axum::routing::get(root))
|
.route("/", axum::routing::get(root))
|
||||||
// .nest("/pages", old_pages::get_routes())
|
.merge(pages::get_routes())
|
||||||
// .merge(old_templates::get_routes())
|
|
||||||
.fallback(fallback)
|
.fallback(fallback)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::{env, io};
|
use std::{env, io};
|
||||||
|
|
||||||
use axum::body::Body;
|
use axum::body::Body;
|
||||||
@ -48,12 +48,14 @@ async fn get_tcp_listener() -> Result<TcpListener, io::Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_livereload_layer(
|
fn get_livereload_layer(
|
||||||
templates_path: &Path,
|
templates_paths: Vec<PathBuf>,
|
||||||
) -> Result<LiveReloadLayer<NotHtmxPredicate>, notify::Error> {
|
) -> Result<LiveReloadLayer<NotHtmxPredicate>, notify::Error> {
|
||||||
let livereload = LiveReloadLayer::new();
|
let livereload = LiveReloadLayer::new();
|
||||||
let reloader = livereload.reloader();
|
let reloader = livereload.reloader();
|
||||||
let mut watcher = notify::recommended_watcher(move |_| reloader.reload())?;
|
let mut watcher = notify::recommended_watcher(move |_| reloader.reload())?;
|
||||||
watcher.watch(templates_path, notify::RecursiveMode::Recursive)?;
|
for templates_path in templates_paths {
|
||||||
|
watcher.watch(templates_path.as_path(), notify::RecursiveMode::Recursive)?;
|
||||||
|
}
|
||||||
Ok(livereload.request_predicate::<Body, NotHtmxPredicate>(NotHtmxPredicate))
|
Ok(livereload.request_predicate::<Body, NotHtmxPredicate>(NotHtmxPredicate))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,10 +65,13 @@ async fn main() -> Result<(), AppError> {
|
|||||||
var: "CARGO_MANIFEST_DIR",
|
var: "CARGO_MANIFEST_DIR",
|
||||||
})?;
|
})?;
|
||||||
let assets_path = Path::new(&manifest_dir).join("assets");
|
let assets_path = Path::new(&manifest_dir).join("assets");
|
||||||
let templates_path = Path::new(&manifest_dir).join("templates");
|
let templates_paths = vec![
|
||||||
|
Path::new(&manifest_dir).join("src/pages"),
|
||||||
|
Path::new(&manifest_dir).join("src/components"),
|
||||||
|
];
|
||||||
|
|
||||||
let livereload_layer =
|
let livereload_layer =
|
||||||
get_livereload_layer(&templates_path).map_err(AppError::NotifyWatcher)?;
|
get_livereload_layer(templates_paths).map_err(AppError::NotifyWatcher)?;
|
||||||
let router = get_router(assets_path.as_path()).layer(livereload_layer);
|
let router = get_router(assets_path.as_path()).layer(livereload_layer);
|
||||||
|
|
||||||
let listener: TcpListener = get_tcp_listener().await.map_err(AppError::TCPListener)?;
|
let listener: TcpListener = get_tcp_listener().await.map_err(AppError::TCPListener)?;
|
||||||
|
20
crates/app/src/menu.rs
Normal file
20
crates/app/src/menu.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
pub struct MenuItem {
|
||||||
|
pub id: String,
|
||||||
|
pub label: String,
|
||||||
|
pub href: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_menu_items() -> Vec<MenuItem> {
|
||||||
|
vec![
|
||||||
|
MenuItem {
|
||||||
|
id: "home".to_string(),
|
||||||
|
label: "Accueil".to_string(),
|
||||||
|
href: "/".to_string(),
|
||||||
|
},
|
||||||
|
MenuItem {
|
||||||
|
id: "cps".to_string(),
|
||||||
|
label: "CPS".to_string(),
|
||||||
|
href: "/cps".to_string(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
use axum::{routing, Router};
|
|
||||||
|
|
||||||
mod cps;
|
|
||||||
mod home;
|
|
||||||
|
|
||||||
pub fn get_routes() -> Router {
|
|
||||||
Router::new()
|
|
||||||
.route("/home", routing::get(home::home))
|
|
||||||
.route("/cps", routing::get(cps::cps))
|
|
||||||
}
|
|
44
crates/app/src/pages/cps.html
Normal file
44
crates/app/src/pages/cps.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% import "navbar/navbar.html" as navbar -%}
|
||||||
|
|
||||||
|
{% block title %}Pharma Libre - CPS{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% call navbar::navbar(current="cps") %}
|
||||||
|
<div class="py-10">
|
||||||
|
<header>
|
||||||
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<h1
|
||||||
|
id="page-title"
|
||||||
|
class="text-3xl font-bold leading-tight tracking-tight text-gray-900"
|
||||||
|
>
|
||||||
|
CPS
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div
|
||||||
|
id="main-container"
|
||||||
|
class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"
|
||||||
|
>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||||
|
<div
|
||||||
|
class="border-2 border-dashed border-gray-300 rounded-lg dark:border-gray-600 h-32 md:h-64"
|
||||||
|
>A</div>
|
||||||
|
<div
|
||||||
|
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
|
||||||
|
>B</div>
|
||||||
|
<div
|
||||||
|
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
|
||||||
|
>C</div>
|
||||||
|
<div
|
||||||
|
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
|
||||||
|
>D</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
|
||||||
|
>E</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -6,7 +6,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "pages/cps.html")]
|
#[template(path = "cps.html")]
|
||||||
pub struct CpsTemplate;
|
pub struct CpsTemplate;
|
||||||
|
|
||||||
pub struct ExtractHxRequest(bool);
|
pub struct ExtractHxRequest(bool);
|
44
crates/app/src/pages/index.html
Normal file
44
crates/app/src/pages/index.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% import "navbar/navbar.html" as navbar -%}
|
||||||
|
|
||||||
|
{% block title %}Pharma Libre - Accueil{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% call navbar::navbar(current="home") %}
|
||||||
|
<div class="py-10">
|
||||||
|
<header>
|
||||||
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<h1
|
||||||
|
id="page-title"
|
||||||
|
class="text-3xl font-bold leading-tight tracking-tight text-gray-900"
|
||||||
|
>
|
||||||
|
Accueil
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div
|
||||||
|
id="main-container"
|
||||||
|
class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"
|
||||||
|
>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||||
|
<div
|
||||||
|
class="border-2 border-dashed border-gray-300 rounded-lg dark:border-gray-600 h-32 md:h-64"
|
||||||
|
>A</div>
|
||||||
|
<div
|
||||||
|
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
|
||||||
|
>B</div>
|
||||||
|
<div
|
||||||
|
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
|
||||||
|
>C</div>
|
||||||
|
<div
|
||||||
|
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
|
||||||
|
>D</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
|
||||||
|
>E</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
7
crates/app/src/pages/mod.rs
Normal file
7
crates/app/src/pages/mod.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use axum::{routing, Router};
|
||||||
|
|
||||||
|
mod cps;
|
||||||
|
|
||||||
|
pub fn get_routes() -> Router {
|
||||||
|
Router::new().route("/cps", routing::get(cps::cps))
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user