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>
|
||||||
florian_briand marked this conversation as resolved
Outdated
|
|||||||
|
</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
Le fond de la
nav
est blanc ainsi que ce titre du coup il n'est pas visible ;)Bien vu ... ce code est totalement faussement compatible avec le mode sombre oO
C'était lié au CSS de Flowbite, qui "surchargeait" inutilement Tailwind tout en faisant bugguer le darkmode.
Corrigé par
d4e5656