some htmx and alpine

This commit is contained in:
2023-08-29 21:33:59 +02:00
parent 3f834cd7d2
commit e0c9bc542a
6 changed files with 612 additions and 189 deletions

View File

@@ -1,4 +1,4 @@
use maud::{html, Markup};
use maud::{html, Markup, PreEscaped};
use crate::models::*;
use crate::ClientState;
@@ -312,12 +312,154 @@ impl InventoryItemList {
}
}
pub struct InventoryNewItemFormName;
impl InventoryNewItemFormName {
pub fn build(value: Option<&str>, error: bool) -> Markup {
html!(
div
."grid"
."grid-cols-[2fr,3fr]"
."justify-items-center"
."items-center"
hx-post="/inventory/item/name/validate"
hx-trigger="input delay:1s, every 5s"
hx-params="new-item-name"
hx-swap="outerHTML"
{
label for="name" .font-bold { "Name" }
input
type="text"
id="new-item-name"
name="new-item-name"
x-on:input="(e) => {save_active = inventory_new_item_check_input()}"
."block"
."w-full"
."p-2"
."bg-gray-50"
."border-2"
."border-red-500"[error]
."border-gray-300"[!error]
."rounded"
."focus:outline-none"
."focus:bg-white"
."focus:border-purple-500"[!error]
value=[value]
;
@if error {
div
."col-start-2"
."text-sm"
."text-red-500"
{ "name already exists" }
}
}
)
}
}
pub struct InventoryNewItemFormWeight;
impl InventoryNewItemFormWeight {
pub fn build() -> Markup {
html!(
div
."grid"
."grid-cols-[2fr,3fr]"
."justify-items-center"
."items-center"
{
label for="weight" .font-bold { "Weight" }
input
type="number"
id="new-item-weight"
name="new-item-weight"
min="0"
x-on:input="(e) => {
save_active = inventory_new_item_check_input();
weight_error = !check_weight();
}"
x-bind:class="weight_error && 'border-red-500' || 'border-gray-300 focus:border-purple-500'"
."block"
."w-full"
."p-2"
."bg-gray-50"
."border-2"
."rounded"
."focus:outline-none"
."focus:bg-white"
{}
span
// x-on produces some errors, this works as well
x-bind:class="!weight_error && 'hidden'"
."col-start-2"
."text-sm"
."text-red-500"
{ "invalid input" }
}
)
}
}
pub struct InventoryNewItemFormCategory;
impl InventoryNewItemFormCategory {
pub fn build(state: &ClientState, categories: &Vec<Category>) -> Markup {
html!(
div
."grid"
."grid-cols-[2fr,3fr]"
."justify-items-center"
."items-center"
{
label for="item-category" .font-bold ."w-1/2" .text-center { "Category" }
select
id="new-item-category-id"
name="new-item-category-id"
."block"
."w-full"
."p-2"
."bg-gray-50"
."border-2"
."border-gray-300"
."rounded"
."focus:outline-none"
."focus:bg-white"
."focus:border-purple-500"
autocomplete="off" // https://stackoverflow.com/a/10096033
{
@for category in categories {
option value=(category.id) selected[state.active_category_id.map_or(false, |id| id == category.id)] {
(category.name)
}
}
}
}
)
}
}
pub struct InventoryNewItemForm;
impl InventoryNewItemForm {
pub fn build(state: &ClientState, categories: &Vec<Category>) -> Markup {
html!(
script {
(PreEscaped("
function inventory_new_item_check_input() {
return document.getElementById('new-item-name').value.length != 0
&& is_positive_integer(document.getElementById('new-item-weight').value)
}
function check_weight() {
return document.getElementById('new-item-weight').validity.valid;
}
"))
}
form
x-data="{
save_active: inventory_new_item_check_input(),
weight_error: !check_weight(),
}"
name="new-item"
id="new-item"
action="/inventory/item/"
@@ -328,74 +470,14 @@ impl InventoryNewItemForm {
span ."mdi" ."mdi-playlist-plus" ."text-2xl" ."mr-4" {}
p ."inline" ."text-xl" { "Add new item" }
}
div ."w-11/12" ."mx-auto" {
div ."pb-8" {
div ."flex" ."flex-row" ."justify-center" ."items-start"{
label for="name" .font-bold ."w-1/2" ."p-2" ."text-center" { "Name" }
span ."w-1/2" {
input type="text" id="new-item-name" name="new-item-name"
."block"
."w-full"
."p-2"
."bg-gray-50"
."border-2"
."rounded"
."focus:outline-none"
."focus:bg-white"
."focus:border-purple-500"
{
}
}
}
}
div ."flex" ."flex-row" ."justify-center" ."items-center" ."pb-8" {
label for="weight" .font-bold ."w-1/2" .text-center { "Weight" }
span ."w-1/2" {
input
type="text"
id="new-item-weight"
name="new-item-weight"
."block"
."w-full"
."p-2"
."bg-gray-50"
."border-2"
."border-gray-300"
."rounded"
."focus:outline-none"
."focus:bg-white"
."focus:border-purple-500"
{
}
}
}
div ."flex" ."flex-row" ."justify-center" ."items-center" ."pb-8" {
label for="item-category" .font-bold ."w-1/2" .text-center { "Category" }
span ."w-1/2" {
select
id="new-item-category-id"
name="new-item-category-id"
."block"
."w-full"
."p-2"
."bg-gray-50"
."border-2"
."border-gray-300"
."rounded"
."focus:outline-none"
."focus:bg-white"
."focus:border-purple-500"
autocomplete="off" // https://stackoverflow.com/a/10096033
{
@for category in categories {
option value=(category.id) selected[state.active_category_id.map_or(false, |id| id == category.id)] {
(category.name)
}
}
}
}
}
div ."w-11/12" ."mx-auto" ."flex" ."flex-col" ."gap-8" {
(InventoryNewItemFormName::build(None, false))
(InventoryNewItemFormWeight::build())
(InventoryNewItemFormCategory::build(&state, categories))
input type="submit" value="Add"
x-bind:disabled="!save_active"
."enabled:cursor-pointer"
."disabled:opacity-50"
."py-2"
."border-2"
."rounded"
@@ -415,6 +497,7 @@ impl InventoryNewCategoryForm {
pub fn build() -> Markup {
html!(
form
x-data="{ save_active: document.getElementById('new-category-name').value.length != 0 }"
name="new-category"
id="new-category"
action="/inventory/category/"
@@ -431,11 +514,13 @@ impl InventoryNewCategoryForm {
label for="name" .font-bold ."w-1/2" ."p-2" ."text-center" { "Name" }
span ."w-1/2" {
input type="text" id="new-category-name" name="new-category-name"
x-on:input="(e) => {save_active = e.target.value.length != 0 }"
."block"
."w-full"
."p-2"
."bg-gray-50"
."border-2"
."border-gray-300"
."rounded"
."focus:outline-none"
."focus:bg-white"
@@ -446,6 +531,9 @@ impl InventoryNewCategoryForm {
}
}
input type="submit" value="Add"
x-bind:disabled="!save_active"
."enabled:cursor-pointer"
."disabled:opacity-50"
."py-2"
."border-2"
."rounded"

View File

@@ -24,6 +24,7 @@ impl Root {
head {
title { "Packager" }
script src="https://unpkg.com/htmx.org@1.9.2" {}
script src="https://unpkg.com/alpinejs@3.12.1" defer {}
script src="https://cdn.tailwindcss.com" {}
script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js" defer {}
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css";

View File

@@ -9,6 +9,9 @@ use serde_variant::to_variant_name;
use crate::ClientState;
pub struct TripManager;
pub mod types;
pub use types::*;
impl TripManager {
pub fn build(trips: Vec<models::Trip>) -> Markup {
html!(
@@ -557,7 +560,7 @@ impl TripInfo {
."gap-1"
{
span { (triptype.name) }
span ."mdi" ."mdi-delete" ."text-sm" {}
span ."mdi" ."mdi-close" ."text-sm" {}
}
}
}
@@ -584,6 +587,7 @@ impl TripInfo {
."flex-column"
."items-center"
."hover:bg-green-200"
."hover:opacity-100"
."gap-1"
."opacity-60"
{
@@ -623,7 +627,9 @@ pub struct TripComment;
impl TripComment {
pub fn build(trip: &models::Trip) -> Markup {
html!(
div {
div
x-data="{ save_active: false }"
{
h1 ."text-xl" ."mb-5" { "Comments" }
form
@@ -636,6 +642,7 @@ impl TripComment {
// https://stackoverflow.com/a/48460773
textarea
#"comment"
x-on:input="save_active=true"
."border" ."w-full" ."h-48"
name="new-comment"
form="edit-comment"
@@ -647,11 +654,14 @@ impl TripComment {
button
type="submit"
form="edit-comment"
x-bind:disabled="!save_active"
."enabled:bg-green-200"
."enabled:hover:bg-green-400"
."enabled:cursor-pointer"
."disabled:opacity-50"
."disabled:bg-gray-300"
."mt-2"
."border"
."bg-green-200"
."hover:bg-green-400"
."cursor-pointer"
."flex"
."flex-column"
."p-2"

View File

@@ -0,0 +1,163 @@
use crate::models;
use crate::ClientState;
use maud::{html, Markup};
pub struct TypeList;
impl TypeList {
pub fn build(state: &ClientState, trip_types: Vec<models::TripsType>) -> Markup {
html!(
div ."p-8" ."flex" ."flex-col" ."gap-8" {
h1 ."text-2xl" {"Trip Types"}
ul
."flex"
."flex-col"
."items-stretch"
."border-t"
."border-l"
."h-full"
{
@for trip_type in trip_types {
li
."border-b"
."border-r"
."flex"
."flex-row"
."justify-between"
."items-stretch"
{
@if state.trip_type_edit.map_or(false, |id| id == trip_type.id) {
form
."hidden"
id="edit-trip-type"
action={ (trip_type.id) "/edit/name/submit" }
target="_self"
method="post"
{}
div
."bg-blue-200"
."p-2"
."grow"
{
input
."bg-blue-100"
."hover:bg-white"
."w-full"
type="text"
name="new-value"
form="edit-trip-type"
value=(trip_type.name)
;
}
div
."flex"
."flex-row"
{
a
href="."
."bg-red-200"
."hover:bg-red-300"
."w-8"
."flex"
{
span
."mdi"
."mdi-cancel"
."text-xl"
."m-auto"
;
}
button
type="submit"
form="edit-trip-type"
."bg-green-200"
."hover:bg-green-300"
."w-8"
{
span
."mdi"
."mdi-content-save"
."text-xl"
;
}
}
} @else {
span
."p-2"
{
(trip_type.name)
}
div
."bg-blue-100"
."hover:bg-blue-200"
."p-0"
."w-8"
{
a
.flex
."w-full"
."h-full"
href={ "?edit=" (trip_type.id) }
{
span
."m-auto"
."mdi"
."mdi-pencil"
."text-xl";
}
}
}
}
}
}
form
name="new-trip-type"
action="/trips/types/"
target="_self"
method="post"
."mt-8" ."p-5" ."border-2" ."border-gray-200"
{
div ."mb-5" ."flex" ."flex-row" {
span ."mdi" ."mdi-playlist-plus" ."text-2xl" ."mr-4" {}
p ."inline" ."text-xl" { "Add new trip type" }
}
div ."w-11/12" ."m-auto" {
div ."mx-auto" ."pb-8" {
div ."flex" ."flex-row" ."justify-center" {
label for="new-trip-type-name" ."font-bold" ."w-1/2" ."p-2" ."text-center" { "Name" }
span ."w-1/2" {
input
type="text"
id="new-trip-type-name"
name="new-trip-type-name"
."block"
."w-full"
."p-2"
."bg-gray-50"
."border-2"
."rounded"
."focus:outline-none"
."focus:bg-white"
{}
}
}
}
input
type="submit"
value="Add"
."py-2"
."border-2"
."rounded"
."border-gray-300"
."mx-auto"
."w-full"
{}
}
}
}
)
}
}