mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Merge branch 'look_and_feel'
This commit is contained in:
commit
6087d31aad
107
Cargo.lock
generated
107
Cargo.lock
generated
@ -224,7 +224,7 @@ checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -383,7 +383,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"shlex",
|
"shlex",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -750,7 +750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
|
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -777,7 +777,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"scratch",
|
"scratch",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -794,7 +794,7 @@ checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -983,7 +983,7 @@ checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1056,14 +1056,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gloo-net"
|
name = "gloo-net"
|
||||||
version = "0.1.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2899cb1a13be9020b010967adc6b2a8a343b6f1428b90238c9d53ca24decc6db"
|
checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"gloo-utils",
|
"gloo-utils",
|
||||||
|
"http",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"serde",
|
"serde",
|
||||||
@ -1088,9 +1089,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gloo-utils"
|
name = "gloo-utils"
|
||||||
version = "0.1.6"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5"
|
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
@ -1233,9 +1234,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.8"
|
version = "0.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@ -1448,10 +1449,10 @@ dependencies = [
|
|||||||
"base64 0.21.0",
|
"base64 0.21.0",
|
||||||
"chrono",
|
"chrono",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
|
"gloo-net",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"maud",
|
"maud",
|
||||||
"recipes",
|
"recipes",
|
||||||
"reqwasm",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sycamore",
|
"sycamore",
|
||||||
@ -1602,7 +1603,7 @@ dependencies = [
|
|||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1658,7 +1659,7 @@ checksum = "731f8ecebd9f3a4aa847dfe75455e4757a45da40a7793d2f0b1f9b6ed18b23f3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1923,7 +1924,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1989,7 +1990,7 @@ dependencies = [
|
|||||||
"proc-macro-error-attr",
|
"proc-macro-error-attr",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2006,9 +2007,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.49"
|
version = "1.0.70"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
|
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@ -2044,9 +2045,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.23"
|
version = "1.0.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@ -2133,15 +2134,6 @@ version = "0.6.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "reqwasm"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "05b89870d729c501fa7a68c43bf4d938bbb3a8c156d333d90faa0e8b3e3212fb"
|
|
||||||
dependencies = [
|
|
||||||
"gloo-net",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.16.20"
|
version = "0.16.20"
|
||||||
@ -2186,7 +2178,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rust-embed-utils",
|
"rust-embed-utils",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2311,7 +2303,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2525,7 +2517,7 @@ dependencies = [
|
|||||||
"sha2 0.10.6",
|
"sha2 0.10.6",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"sqlx-rt",
|
"sqlx-rt",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2618,7 +2610,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2653,7 +2645,7 @@ dependencies = [
|
|||||||
"nom",
|
"nom",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2694,6 +2686,17 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sync_wrapper"
|
name = "sync_wrapper"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -2732,7 +2735,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2823,7 +2826,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2938,7 +2941,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3158,28 +3161,26 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.84"
|
version = "0.2.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
|
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.84"
|
version = "0.2.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
|
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.39",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3197,9 +3198,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.84"
|
version = "0.2.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
|
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@ -3207,22 +3208,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.84"
|
version = "0.2.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.39",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.84"
|
version = "0.2.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-test"
|
name = "wasm-bindgen-test"
|
||||||
@ -3269,7 +3270,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"str_inflector",
|
"str_inflector",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [ "recipes", "kitchen", "web", "api" ]
|
members = [ "recipes", "kitchen", "web", "api" ]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# TODO(jwall): When the fix for RcSignal Binding is released we can drop this patch.
|
# TODO(jwall): When the fix for RcSignal Binding is released we can drop this patch.
|
||||||
|
16
Makefile
16
Makefile
@ -14,6 +14,11 @@
|
|||||||
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
|
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
|
||||||
mkfile_dir := $(dir $(mkfile_path))
|
mkfile_dir := $(dir $(mkfile_path))
|
||||||
sqlite_url := sqlite://$(mkfile_dir)/.session_store/store.db
|
sqlite_url := sqlite://$(mkfile_dir)/.session_store/store.db
|
||||||
|
out := dist
|
||||||
|
project := kitchen
|
||||||
|
|
||||||
|
export out
|
||||||
|
export kitchen
|
||||||
|
|
||||||
kitchen: wasm kitchen/src/*.rs
|
kitchen: wasm kitchen/src/*.rs
|
||||||
cd kitchen; cargo build
|
cd kitchen; cargo build
|
||||||
@ -27,15 +32,18 @@ static-prep: web/index.html web/favicon.ico web/static/*.css
|
|||||||
cp -r web/favicon.ico web/dist/
|
cp -r web/favicon.ico web/dist/
|
||||||
cp -r web/static web/dist/
|
cp -r web/static web/dist/
|
||||||
|
|
||||||
wasmrelease: wasmrelease-dist static-prep
|
wasmrelease: wasm-opt static-prep
|
||||||
|
|
||||||
|
wasm-opt: wasmrelease-dist
|
||||||
|
cd web; sh ../scripts/wasm-opt.sh release
|
||||||
|
|
||||||
wasmrelease-dist: web/src/*.rs web/src/components/*.rs
|
wasmrelease-dist: web/src/*.rs web/src/components/*.rs
|
||||||
cd web; wasm-pack build --mode no-install --release --target web --no-typescript --out-name kitchen_wasm --out-dir dist/
|
cd web; sh ../scripts/wasm-build.sh release
|
||||||
|
|
||||||
wasm: wasm-dist static-prep
|
wasm: wasm-dist static-prep
|
||||||
|
|
||||||
wasm-dist: web/src/*.rs web/src/components/*.rs
|
wasm-dist: web/src/*.rs web/src/components/*.rs
|
||||||
cd web; wasm-pack build --mode no-install --target web --no-typescript --out-dir dist/ --features debug_logs
|
cd web; sh ../scripts/wasm-build.sh debug
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf web/dist/*
|
rm -rf web/dist/*
|
||||||
@ -50,5 +58,5 @@ sqlx-add-%:
|
|||||||
sqlx-revert:
|
sqlx-revert:
|
||||||
cd kitchen; cargo sqlx migrate revert --database-url $(sqlite_url)
|
cd kitchen; cargo sqlx migrate revert --database-url $(sqlite_url)
|
||||||
|
|
||||||
sqlx-prepare:
|
sqlx-prepare: kitchen
|
||||||
cd kitchen; cargo sqlx prepare --database-url $(sqlite_url)
|
cd kitchen; cargo sqlx prepare --database-url $(sqlite_url)
|
||||||
|
13
flake.lock
generated
13
flake.lock
generated
@ -99,11 +99,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1679174867,
|
"lastModified": 1719152388,
|
||||||
"narHash": "sha256-fFxb8wN3bjOMvHPr63Iyzo3cuHhQzWW03UkckfTeBWU=",
|
"narHash": "sha256-pHg0nzAa2ZM+zFamfsY7ZvVaB19pMr5wl4G5nO0J7eU=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "f5ec87b82832736f1624874fd34eb60c0b68bdd6",
|
"rev": "be54c7d931a68ba6a79f097ce979288e90a74288",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -124,17 +124,16 @@
|
|||||||
},
|
},
|
||||||
"rust-overlay": {
|
"rust-overlay": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils_2",
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1702001829,
|
"lastModified": 1718681902,
|
||||||
"narHash": "sha256-6gEVidNVqzTb06zIy2Gxhz9m6/jXyAgViRxfgEpZkQ8=",
|
"narHash": "sha256-E/T7Ge6ayEQe7FVKMJqDBoHyLhRhjc6u9CmU8MyYfy0=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "c2a1dd067a928624c1aab36f976758c0722c79bd",
|
"rev": "16c8ad83297c278eebe740dea5491c1708960dd1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
10
flake.nix
10
flake.nix
@ -25,7 +25,7 @@
|
|||||||
let
|
let
|
||||||
overlays = [ rust-overlay.overlays.default ];
|
overlays = [ rust-overlay.overlays.default ];
|
||||||
pkgs = import nixpkgs { inherit system overlays; };
|
pkgs = import nixpkgs { inherit system overlays; };
|
||||||
rust-wasm = pkgs.rust-bin.stable."1.74.1".default.override {
|
rust-wasm = pkgs.rust-bin.stable."1.77.0".default.override {
|
||||||
extensions = [ "rust-src" ];
|
extensions = [ "rust-src" ];
|
||||||
# Add wasm32 as an extra target besides the native target.
|
# Add wasm32 as an extra target besides the native target.
|
||||||
targets = [ "wasm32-unknown-unknown" ];
|
targets = [ "wasm32-unknown-unknown" ];
|
||||||
@ -45,6 +45,14 @@
|
|||||||
wasm-bindgen = pkgs.callPackage wasm-bindgenGen { inherit pkgs; };
|
wasm-bindgen = pkgs.callPackage wasm-bindgenGen { inherit pkgs; };
|
||||||
kitchenWasm = kitchenWasmGen {
|
kitchenWasm = kitchenWasmGen {
|
||||||
inherit pkgs rust-wasm wasm-bindgen version;
|
inherit pkgs rust-wasm wasm-bindgen version;
|
||||||
|
lockFile = ./Cargo.lock;
|
||||||
|
outputHashes = {
|
||||||
|
# I'm maintaining some patches for these so the lockfile hashes are a little
|
||||||
|
# incorrect. We override those here.
|
||||||
|
"wasm-web-component-0.2.0" = "sha256-quuPgzGb2F96blHmD3BAUjsWQYbSyJGZl27PVrwL92k=";
|
||||||
|
"sycamore-0.8.2" = "sha256-D968+8C5EelGGmot9/LkAlULZOf/Cr+1WYXRCMwb1nQ=";
|
||||||
|
"sqlx-0.6.2" = "sha256-X/LFvtzRfiOIEZJiVzmFvvULPpjhqvI99pSwH7a//GM=";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
kitchen = (kitchenGen {
|
kitchen = (kitchenGen {
|
||||||
inherit pkgs version naersk-lib kitchenWasm rust-wasm;
|
inherit pkgs version naersk-lib kitchenWasm rust-wasm;
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- Add down migration script here
|
||||||
|
ALTER TABLE recipes DROP COLUMN serving_count;
|
2
kitchen/migrations/20240701002811_recipe-servings.up.sql
Normal file
2
kitchen/migrations/20240701002811_recipe-servings.up.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
-- Add up migration script here
|
||||||
|
ALTER TABLE recipes ADD column serving_count number;
|
@ -1,4 +1,3 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
// Copyright 2022 Jeremy Wall
|
// Copyright 2022 Jeremy Wall
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -12,6 +11,7 @@ use std::collections::BTreeMap;
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{collections::BTreeSet, net::SocketAddr};
|
use std::{collections::BTreeSet, net::SocketAddr};
|
||||||
|
51
models/browser_state.als
Normal file
51
models/browser_state.als
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
sig Id {}
|
||||||
|
sig Text {}
|
||||||
|
|
||||||
|
sig Recipe {
|
||||||
|
, id: one Id
|
||||||
|
, text: one Text
|
||||||
|
}
|
||||||
|
|
||||||
|
fact {
|
||||||
|
no r1, r2: Recipe | (r1.id = r2.id) and (r1.text != r2.text)
|
||||||
|
no r1, r2: Recipe | (r1 != r2) and (r1.id = r2.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig Ingredient {}
|
||||||
|
sig Modifier {}
|
||||||
|
sig Amt {}
|
||||||
|
|
||||||
|
sig ModifiedInventory {
|
||||||
|
, ingredient: one Ingredient
|
||||||
|
, modifier: lone Modifier
|
||||||
|
, amt: one Amt
|
||||||
|
}
|
||||||
|
|
||||||
|
fact {
|
||||||
|
no mi1, mi2: ModifiedInventory | mi1 != mi2 && (mi1.ingredient = mi2.ingredient) and (mi1.modifier = mi2.modifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig DeletedInventory {
|
||||||
|
, ingredient: one Ingredient
|
||||||
|
, modifier: lone Modifier
|
||||||
|
}
|
||||||
|
|
||||||
|
fact {
|
||||||
|
no mi1, mi2: DeletedInventory | mi1 != mi2 && (mi1.ingredient = mi2.ingredient) and (mi1.modifier = mi2.modifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig ExtraItems {
|
||||||
|
, ingredient: one Ingredient
|
||||||
|
, amt: one Amt
|
||||||
|
}
|
||||||
|
|
||||||
|
sig State {
|
||||||
|
, recipes: some Recipe
|
||||||
|
, modified: set ModifiedInventory
|
||||||
|
, deleted: set DeletedInventory
|
||||||
|
, extras: set ExtraItems
|
||||||
|
} {
|
||||||
|
no rs: Recipe | rs not in recipes
|
||||||
|
}
|
||||||
|
|
||||||
|
run { } for 3 but exactly 2 State, 2 Modifier, exactly 3 ModifiedInventory, exactly 9 Ingredient
|
17
models/planning.d2
Normal file
17
models/planning.d2
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
Meal Planning: {
|
||||||
|
shape: sequence_diagram
|
||||||
|
user: Cook; client: Kitchen frontend; kitchen: Kitchen backend
|
||||||
|
|
||||||
|
user -> client: Start new meal Plan
|
||||||
|
client -> kitchen: new plan created
|
||||||
|
user -> client: Add recipe to meal plan
|
||||||
|
client -> kitchen: Update meal plan with recipe
|
||||||
|
client -> client: cache updated meal plan
|
||||||
|
user -> client: Do inventory
|
||||||
|
client -> kitchen: Store inventory mutations
|
||||||
|
client -> client: cache inventory mutations
|
||||||
|
user -> client: Undo mutation
|
||||||
|
client -> kitchen: Store inventory mutations
|
||||||
|
client -> client: cache inventory mutations
|
||||||
|
user -> user: Cook recipes
|
||||||
|
}
|
125
models/planning.svg
Normal file
125
models/planning.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 94 KiB |
@ -3,23 +3,18 @@
|
|||||||
features ? "",
|
features ? "",
|
||||||
rust-wasm,
|
rust-wasm,
|
||||||
wasm-bindgen,
|
wasm-bindgen,
|
||||||
|
lockFile,
|
||||||
|
outputHashes,
|
||||||
}:
|
}:
|
||||||
with pkgs;
|
with pkgs;
|
||||||
let
|
let
|
||||||
pname = "kitchen-wasm";
|
pname = "kitchen-wasm";
|
||||||
src = ./../..;
|
src = ./../..;
|
||||||
lockFile = ./../../Cargo.lock;
|
|
||||||
# NOTE(jwall): Because we use wasm-pack directly below we need
|
# NOTE(jwall): Because we use wasm-pack directly below we need
|
||||||
# the cargo dependencies to already be installed.
|
# the cargo dependencies to already be installed.
|
||||||
cargoDeps = (pkgs.rustPlatform.importCargoLock { inherit lockFile; outputHashes = {
|
cargoDeps = (pkgs.rustPlatform.importCargoLock { inherit lockFile outputHashes; });
|
||||||
# I'm maintaining some patches for these so the lockfile hashes are a little
|
|
||||||
# incorrect. We override those here.
|
|
||||||
"wasm-web-component-0.2.0" = "sha256-quuPgzGb2F96blHmD3BAUjsWQYbSyJGZl27PVrwL92k=";
|
|
||||||
"sycamore-0.8.2" = "sha256-D968+8C5EelGGmot9/LkAlULZOf/Cr+1WYXRCMwb1nQ=";
|
|
||||||
"sqlx-0.6.2" = "sha256-X/LFvtzRfiOIEZJiVzmFvvULPpjhqvI99pSwH7a//GM=";
|
|
||||||
};
|
|
||||||
});
|
|
||||||
in
|
in
|
||||||
|
# TODO(zaphar): I should actually be leveraging naersklib.buildPackage with a postInstall for the optimization and bindgen
|
||||||
stdenv.mkDerivation {
|
stdenv.mkDerivation {
|
||||||
inherit src pname;
|
inherit src pname;
|
||||||
version = version;
|
version = version;
|
||||||
@ -34,12 +29,19 @@ stdenv.mkDerivation {
|
|||||||
'';
|
'';
|
||||||
# TODO(jwall): Build this from the root rather than the src.
|
# TODO(jwall): Build this from the root rather than the src.
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
echo building with wasm-pack
|
|
||||||
mkdir -p $out
|
mkdir -p $out
|
||||||
cd web
|
cd web
|
||||||
cp -r static $out
|
cp -r static $out
|
||||||
RUST_LOG=info wasm-pack build --mode no-install --release --target web --out-dir $out ${features};
|
sh ../scripts/wasm-build.sh release
|
||||||
|
#cargo build --lib --release --target wasm32-unknown-unknown --target-dir $out ${features} --offline
|
||||||
|
#wasm-bindgen $out/wasm32-unknown-unknown/release/kitchen_wasm.wasm --out-dir $out --typescript --target web
|
||||||
|
#sh ../scripts/wasm-opt.sh release
|
||||||
|
wasm-opt $out/kitchen_wasm_bg.wasm -o $out/kitchen_wasm_bg-opt.wasm -O
|
||||||
|
rm -f $out/kitchen_wasm_bg.wasm
|
||||||
|
mv $out/kitchen_wasm_bg-opt.wasm $out/kitchen_wasm_bg.wasm
|
||||||
cp -r index.html $out
|
cp -r index.html $out
|
||||||
cp -r favicon.ico $out
|
cp -r favicon.ico $out
|
||||||
|
rm -rf $out/release
|
||||||
|
rm -rf $out/wasm32-unknown-unknown
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,7 @@ in
|
|||||||
, nodejs
|
, nodejs
|
||||||
, pkg-config
|
, pkg-config
|
||||||
, openssl
|
, openssl
|
||||||
, stdenv
|
|
||||||
, curl
|
, curl
|
||||||
, runCommand
|
|
||||||
}:
|
}:
|
||||||
|
|
||||||
# This package is special so we don't use the naersk infrastructure to build it.
|
# This package is special so we don't use the naersk infrastructure to build it.
|
||||||
@ -20,14 +18,14 @@ rustPlatform.buildRustPackage rec {
|
|||||||
pname = "wasm-bindgen-cli";
|
pname = "wasm-bindgen-cli";
|
||||||
# NOTE(jwall): This must exactly match the version of the wasm-bindgen crate
|
# NOTE(jwall): This must exactly match the version of the wasm-bindgen crate
|
||||||
# we are using.
|
# we are using.
|
||||||
version = "0.2.84";
|
version = "0.2.89";
|
||||||
|
|
||||||
src = fetchCrate {
|
src = fetchCrate {
|
||||||
inherit pname version;
|
inherit pname version;
|
||||||
sha256 = "sha256-0rK+Yx4/Jy44Fw5VwJ3tG243ZsyOIBBehYU54XP/JGk=";
|
sha256 = "sha256-IPxP68xtNSpwJjV2yNMeepAS0anzGl02hYlSTvPocz8=";
|
||||||
};
|
};
|
||||||
|
|
||||||
cargoSha256 = "sha256-vcpxcRlW1OKoD64owFF6mkxSqmNrvY+y3Ckn5UwEQ50=";
|
cargoSha256 = "sha256-pBeQaG6i65uJrJptZQLuIaCb/WCQMhba1Z1OhYqA8Zc=";
|
||||||
|
|
||||||
nativeBuildInputs = [ pkg-config ];
|
nativeBuildInputs = [ pkg-config ];
|
||||||
|
|
||||||
@ -36,5 +34,5 @@ rustPlatform.buildRustPackage rec {
|
|||||||
nativeCheckInputs = [ nodejs ];
|
nativeCheckInputs = [ nodejs ];
|
||||||
|
|
||||||
# other tests require it to be ran in the wasm-bindgen monorepo
|
# other tests require it to be ran in the wasm-bindgen monorepo
|
||||||
cargoTestFlags = [ "--test=interface-types" ];
|
cargoTestFlags = [ "--test=reference" ];
|
||||||
}
|
}
|
@ -6,10 +6,6 @@ A web assembly experiment in Meal Planning and Shopping List management.
|
|||||||
|
|
||||||
Ensure you have rust installed with support for the web assembly target. You can see instructions here: [Rust wasm book](https://rustwasm.github.io/docs/book/game-of-life/setup.html).
|
Ensure you have rust installed with support for the web assembly target. You can see instructions here: [Rust wasm book](https://rustwasm.github.io/docs/book/game-of-life/setup.html).
|
||||||
|
|
||||||
You will also want to have trunk installed. You can see instructions for that here: [trunk](https://trunkrs.dev/)
|
|
||||||
|
|
||||||
Then obtain the source. We do not at this time publish kitchen on [crates.io](https://crates.io/).
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/zaphar/kitchen
|
git clone https://github.com/zaphar/kitchen
|
||||||
cd kitchen
|
cd kitchen
|
||||||
@ -23,7 +19,7 @@ make release
|
|||||||
|
|
||||||
# Hacking on kitchen
|
# Hacking on kitchen
|
||||||
|
|
||||||
If you want to hack on kitchen, then you may find it useful to use trunk in dev mode. The run script will run build the app and run trunk with it watching for changes and reloading on demand in your browser.
|
The run script will run build the app and run it for you.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./run.sh
|
./run.sh
|
||||||
|
@ -156,16 +156,28 @@ impl IngredientAccumulator {
|
|||||||
set.insert(recipe_title.clone());
|
set.insert(recipe_title.clone());
|
||||||
self.inner.insert(key, (i.clone(), set));
|
self.inner.insert(key, (i.clone(), set));
|
||||||
} else {
|
} else {
|
||||||
let amt = match (self.inner[&key].0.amt, i.amt) {
|
let amts = match (&self.inner[&key].0.amt, &i.amt) {
|
||||||
(Volume(rvm), Volume(lvm)) => Volume(lvm + rvm),
|
(Volume(rvm), Volume(lvm)) => vec![Volume(lvm + rvm)],
|
||||||
(Count(lqty), Count(rqty)) => Count(lqty + rqty),
|
(Count(lqty), Count(rqty)) => vec![Count(lqty + rqty)],
|
||||||
(Weight(lqty), Weight(rqty)) => Weight(lqty + rqty),
|
(Weight(lqty), Weight(rqty)) => vec![Weight(lqty + rqty)],
|
||||||
|
(Package(lnm, lqty), Package(rnm, rqty)) => {
|
||||||
|
if lnm == rnm {
|
||||||
|
vec![Package(lnm.clone(), lqty + rqty)]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
Package(lnm.clone(), lqty.clone()),
|
||||||
|
Package(rnm.clone(), rqty.clone()),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
self.inner.get_mut(&key).map(|(i, set)| {
|
for amt in amts {
|
||||||
i.amt = amt;
|
self.inner.get_mut(&key).map(|(i, set)| {
|
||||||
set.insert(recipe_title.clone());
|
i.amt = amt;
|
||||||
});
|
set.insert(recipe_title.clone());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -334,7 +334,14 @@ make_fn!(unit<StrIter, String>,
|
|||||||
text_token!("kg"),
|
text_token!("kg"),
|
||||||
text_token!("grams"),
|
text_token!("grams"),
|
||||||
text_token!("gram"),
|
text_token!("gram"),
|
||||||
text_token!("g")),
|
text_token!("g"),
|
||||||
|
text_token!("pkg"),
|
||||||
|
text_token!("package"),
|
||||||
|
text_token!("bottle"),
|
||||||
|
text_token!("bot"),
|
||||||
|
text_token!("bag"),
|
||||||
|
text_token!("can")
|
||||||
|
),
|
||||||
_ => ws,
|
_ => ws,
|
||||||
(u.to_lowercase().to_singular())
|
(u.to_lowercase().to_singular())
|
||||||
)
|
)
|
||||||
@ -393,6 +400,7 @@ pub fn measure(i: StrIter) -> abortable_parser::Result<StrIter, Measure> {
|
|||||||
"oz" => Weight(Oz(qty)),
|
"oz" => Weight(Oz(qty)),
|
||||||
"kg" | "kilogram" => Weight(Kilogram(qty)),
|
"kg" | "kilogram" => Weight(Kilogram(qty)),
|
||||||
"g" | "gram" => Weight(Gram(qty)),
|
"g" | "gram" => Weight(Gram(qty)),
|
||||||
|
"pkg" | "package" | "can" | "bag" | "bottle" | "bot" => Measure::pkg(s, qty),
|
||||||
_u => {
|
_u => {
|
||||||
eprintln!("Invalid unit: {}", _u);
|
eprintln!("Invalid unit: {}", _u);
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@ -418,9 +426,8 @@ pub fn normalize_name(name: &str) -> String {
|
|||||||
// NOTE(jwall): The below unwrap is safe because of the length
|
// NOTE(jwall): The below unwrap is safe because of the length
|
||||||
// check above.
|
// check above.
|
||||||
let last = parts.last().unwrap();
|
let last = parts.last().unwrap();
|
||||||
let normalized = last.to_singular();
|
|
||||||
prefix.push(' ');
|
prefix.push(' ');
|
||||||
prefix.push_str(&normalized);
|
prefix.push_str(&last.to_string());
|
||||||
return prefix;
|
return prefix;
|
||||||
}
|
}
|
||||||
return name.trim().to_lowercase().to_owned();
|
return name.trim().to_lowercase().to_owned();
|
||||||
|
@ -235,32 +235,30 @@ fn test_ingredient_name_parse() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_ingredient_parse() {
|
fn test_ingredient_parse() {
|
||||||
for (i, expected) in vec![
|
for (i, expected) in vec![
|
||||||
//(
|
(
|
||||||
// "1 cup flour ",
|
"1 cup flour ",
|
||||||
// Ingredient::new("flour", None, Volume(Cup(Quantity::Whole(1))), ""),
|
Ingredient::new("flour", None, Volume(Cup(Quantity::Whole(1)))),
|
||||||
//),
|
),
|
||||||
//(
|
(
|
||||||
// "\t1 cup flour ",
|
"\t1 cup flour ",
|
||||||
// Ingredient::new("flour", None, Volume(Cup(Quantity::Whole(1))), ""),
|
Ingredient::new("flour", None, Volume(Cup(Quantity::Whole(1)))),
|
||||||
//),
|
),
|
||||||
//(
|
(
|
||||||
// "1 cup apple (chopped)",
|
"1 cup apple (chopped)",
|
||||||
// Ingredient::new(
|
Ingredient::new(
|
||||||
// "apple",
|
"apple",
|
||||||
// Some("chopped".to_owned()),
|
Some("chopped".to_owned()),
|
||||||
// Volume(Cup(Quantity::Whole(1))),
|
Volume(Cup(Quantity::Whole(1))),
|
||||||
// "",
|
),
|
||||||
// ),
|
),
|
||||||
//),
|
(
|
||||||
//(
|
"1 cup apple (chopped) ",
|
||||||
// "1 cup apple (chopped) ",
|
Ingredient::new(
|
||||||
// Ingredient::new(
|
"apple",
|
||||||
// "apple",
|
Some("chopped".to_owned()),
|
||||||
// Some("chopped".to_owned()),
|
Volume(Cup(Quantity::Whole(1))),
|
||||||
// Volume(Cup(Quantity::Whole(1))),
|
),
|
||||||
// "",
|
),
|
||||||
// ),
|
|
||||||
//),
|
|
||||||
(
|
(
|
||||||
"1 green bell pepper (chopped) ",
|
"1 green bell pepper (chopped) ",
|
||||||
Ingredient::new(
|
Ingredient::new(
|
||||||
@ -269,6 +267,46 @@ fn test_ingredient_parse() {
|
|||||||
Count(Quantity::Whole(1)),
|
Count(Quantity::Whole(1)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"1 pkg green onion",
|
||||||
|
Ingredient::new(
|
||||||
|
"green onion",
|
||||||
|
None,
|
||||||
|
Package("pkg".into(), Quantity::Whole(1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"1 bottle green onion",
|
||||||
|
Ingredient::new(
|
||||||
|
"green onion",
|
||||||
|
None,
|
||||||
|
Package("bottle".into(), Quantity::Whole(1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"1 bot green onion",
|
||||||
|
Ingredient::new(
|
||||||
|
"green onion",
|
||||||
|
None,
|
||||||
|
Package("bot".into(), Quantity::Whole(1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"1 bag green onion",
|
||||||
|
Ingredient::new(
|
||||||
|
"green onion",
|
||||||
|
None,
|
||||||
|
Package("bag".into(), Quantity::Whole(1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"1 can baked beans",
|
||||||
|
Ingredient::new(
|
||||||
|
"baked beans",
|
||||||
|
None,
|
||||||
|
Package("can".into(), Quantity::Whole(1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
] {
|
] {
|
||||||
match parse::ingredient(StrIter::new(i)) {
|
match parse::ingredient(StrIter::new(i)) {
|
||||||
ParseResult::Complete(_, ing) => assert_eq!(ing, expected),
|
ParseResult::Complete(_, ing) => assert_eq!(ing, expected),
|
||||||
|
@ -22,6 +22,7 @@ use std::{
|
|||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
ops::{Add, Div, Mul, Sub},
|
ops::{Add, Div, Mul, Sub},
|
||||||
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use num_rational::Ratio;
|
use num_rational::Ratio;
|
||||||
@ -179,6 +180,20 @@ impl VolumeMeasure {
|
|||||||
|
|
||||||
macro_rules! volume_op {
|
macro_rules! volume_op {
|
||||||
($trait:ident, $method:ident) => {
|
($trait:ident, $method:ident) => {
|
||||||
|
impl $trait for &VolumeMeasure {
|
||||||
|
type Output = VolumeMeasure;
|
||||||
|
|
||||||
|
fn $method(self, lhs: Self) -> Self::Output {
|
||||||
|
let (l, r) = (self.get_ml(), lhs.get_ml());
|
||||||
|
let result = ML($trait::$method(l, r));
|
||||||
|
if self.metric() {
|
||||||
|
result.normalize()
|
||||||
|
} else {
|
||||||
|
result.into_tsp().normalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl $trait for VolumeMeasure {
|
impl $trait for VolumeMeasure {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
@ -293,6 +308,20 @@ impl WeightMeasure {
|
|||||||
|
|
||||||
macro_rules! weight_op {
|
macro_rules! weight_op {
|
||||||
($trait:ident, $method:ident) => {
|
($trait:ident, $method:ident) => {
|
||||||
|
impl $trait for &WeightMeasure {
|
||||||
|
type Output = WeightMeasure;
|
||||||
|
|
||||||
|
fn $method(self, lhs: Self) -> Self::Output {
|
||||||
|
let (l, r) = (self.get_grams(), lhs.get_grams());
|
||||||
|
let result = WeightMeasure::Gram($trait::$method(l, r));
|
||||||
|
if self.metric() {
|
||||||
|
result.normalize()
|
||||||
|
} else {
|
||||||
|
result.into_oz().normalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl $trait for WeightMeasure {
|
impl $trait for WeightMeasure {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
@ -335,18 +364,19 @@ impl Display for WeightMeasure {
|
|||||||
|
|
||||||
use WeightMeasure::{Gram, Kilogram, Oz, Pound};
|
use WeightMeasure::{Gram, Kilogram, Oz, Pound};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
|
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
|
||||||
/// Measurements in a Recipe with associated units for them.
|
/// Measurements in a Recipe with associated units for them.
|
||||||
pub enum Measure {
|
pub enum Measure {
|
||||||
/// Volume measurements as meter cubed base unit
|
/// Volume measurements as meter cubed base unit
|
||||||
Volume(VolumeMeasure),
|
Volume(VolumeMeasure),
|
||||||
/// Simple count of items
|
/// Simple count of items
|
||||||
Count(Quantity),
|
Count(Quantity),
|
||||||
|
Package(Rc<str>, Quantity),
|
||||||
/// Weight measure as Grams base unit
|
/// Weight measure as Grams base unit
|
||||||
Weight(WeightMeasure),
|
Weight(WeightMeasure),
|
||||||
}
|
}
|
||||||
|
|
||||||
use Measure::{Count, Volume, Weight};
|
use Measure::{Count, Package, Volume, Weight};
|
||||||
|
|
||||||
impl Measure {
|
impl Measure {
|
||||||
pub fn tsp(qty: Quantity) -> Self {
|
pub fn tsp(qty: Quantity) -> Self {
|
||||||
@ -407,11 +437,16 @@ impl Measure {
|
|||||||
Weight(Oz(qty))
|
Weight(Oz(qty))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pkg<S: Into<Rc<str>>>(name: S, qty: Quantity) -> Self {
|
||||||
|
Package(name.into(), qty)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn measure_type(&self) -> String {
|
pub fn measure_type(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Volume(_) => "Volume",
|
Volume(_) => "Volume",
|
||||||
Count(_) => "Count",
|
Count(_) => "Count",
|
||||||
Weight(_) => "Weight",
|
Weight(_) => "Weight",
|
||||||
|
Package(_, _) => "Package",
|
||||||
}
|
}
|
||||||
.to_owned()
|
.to_owned()
|
||||||
}
|
}
|
||||||
@ -421,6 +456,7 @@ impl Measure {
|
|||||||
Volume(vm) => vm.plural(),
|
Volume(vm) => vm.plural(),
|
||||||
Count(qty) => qty.plural(),
|
Count(qty) => qty.plural(),
|
||||||
Weight(wm) => wm.plural(),
|
Weight(wm) => wm.plural(),
|
||||||
|
Package(_, qty) => qty.plural(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,6 +465,7 @@ impl Measure {
|
|||||||
Volume(vm) => Volume(vm.normalize()),
|
Volume(vm) => Volume(vm.normalize()),
|
||||||
Count(qty) => Count(qty.clone()),
|
Count(qty) => Count(qty.clone()),
|
||||||
Weight(wm) => Weight(wm.normalize()),
|
Weight(wm) => Weight(wm.normalize()),
|
||||||
|
Package(nm, qty) => Package(nm.clone(), qty.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -439,6 +476,7 @@ impl Display for Measure {
|
|||||||
Volume(vm) => write!(w, "{}", vm),
|
Volume(vm) => write!(w, "{}", vm),
|
||||||
Count(qty) => write!(w, "{}", qty),
|
Count(qty) => write!(w, "{}", qty),
|
||||||
Weight(wm) => write!(w, "{}", wm),
|
Weight(wm) => write!(w, "{}", wm),
|
||||||
|
Package(nm, qty) => write!(w, "{} {}", qty, nm),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -533,6 +571,26 @@ impl TryFrom<f32> for Quantity {
|
|||||||
|
|
||||||
macro_rules! quantity_op {
|
macro_rules! quantity_op {
|
||||||
($trait:ident, $method:ident) => {
|
($trait:ident, $method:ident) => {
|
||||||
|
impl $trait for &Quantity {
|
||||||
|
type Output = Quantity;
|
||||||
|
|
||||||
|
fn $method(self, lhs: Self) -> Self::Output {
|
||||||
|
match (self, lhs) {
|
||||||
|
(Whole(rhs), Whole(lhs)) => Frac($trait::$method(
|
||||||
|
Ratio::from_integer(*rhs),
|
||||||
|
Ratio::from_integer(*lhs),
|
||||||
|
)),
|
||||||
|
(Frac(rhs), Frac(lhs)) => Frac($trait::$method(rhs, lhs)),
|
||||||
|
(Whole(rhs), Frac(lhs)) => {
|
||||||
|
Frac($trait::$method(Ratio::from_integer(*rhs), lhs))
|
||||||
|
}
|
||||||
|
(Frac(rhs), Whole(lhs)) => {
|
||||||
|
Frac($trait::$method(rhs, Ratio::from_integer(*lhs)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl $trait for Quantity {
|
impl $trait for Quantity {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
|
9
scripts/wasm-build.sh
Normal file
9
scripts/wasm-build.sh
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
set -x
|
||||||
|
buildtype=$1;
|
||||||
|
|
||||||
|
if [ ${buildtype} = "release" ]; then
|
||||||
|
buildtype_flag="--release"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cargo build --lib ${buildtype_flag} --target wasm32-unknown-unknown --target-dir $out --features debug_logs
|
||||||
|
wasm-bindgen $out/wasm32-unknown-unknown/${buildtype}/kitchen_wasm.wasm --out-dir $out --typescript --target web
|
6
scripts/wasm-opt.sh
Normal file
6
scripts/wasm-opt.sh
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
set -x
|
||||||
|
buildtype=$1;
|
||||||
|
|
||||||
|
wasm-opt $out/wasm32-unknown-unkown/${buildtype}/${project}_wasm.wasm --out-dir dist/ -O
|
||||||
|
rm -f $out/${project}_wasm_bg.wasm
|
||||||
|
mv $out/${project}_wasm_bg-opt.wasm dist/${project}_wasm_bg.wasm
|
@ -43,12 +43,12 @@ features = ["fmt", "time"]
|
|||||||
version = "0.4.22"
|
version = "0.4.22"
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|
||||||
[dependencies.reqwasm]
|
[dependencies.gloo-net]
|
||||||
version = "0.5.0"
|
version = "0.4.0"
|
||||||
|
|
||||||
[dependencies.wasm-bindgen]
|
[dependencies.wasm-bindgen]
|
||||||
# we need wasm-bindgen v0.2.84 exactly
|
# we need wasm-bindgen v0.2.84 exactly
|
||||||
version = "= 0.2.84"
|
version = "= 0.2.89"
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
@ -56,6 +56,7 @@ features = [
|
|||||||
"Event",
|
"Event",
|
||||||
"InputEvent",
|
"InputEvent",
|
||||||
"CustomEvent",
|
"CustomEvent",
|
||||||
|
"CustomEventInit",
|
||||||
"EventTarget",
|
"EventTarget",
|
||||||
"History",
|
"History",
|
||||||
"HtmlAnchorElement",
|
"HtmlAnchorElement",
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" name="viewport"
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" name="viewport"
|
||||||
content="width=device-width, initial-scale=1.0" charset="UTF-8">
|
content="width=device-width, initial-scale=1.0" charset="UTF-8">
|
||||||
<link rel="stylesheet" href="/ui/static/pico.min.css">
|
<link rel="stylesheet" href="/ui/static/normalize.css">
|
||||||
<link rel="stylesheet" href="/ui/static/app.css">
|
<link rel="stylesheet" href="/ui/static/app.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
134
web/src/api.rs
134
web/src/api.rs
@ -15,7 +15,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
|||||||
|
|
||||||
use base64::{self, Engine};
|
use base64::{self, Engine};
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use reqwasm;
|
use gloo_net;
|
||||||
use serde_json::{from_str, to_string};
|
use serde_json::{from_str, to_string};
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use tracing::{debug, error, instrument};
|
use tracing::{debug, error, instrument};
|
||||||
@ -25,7 +25,10 @@ use recipes::{IngredientKey, RecipeEntry};
|
|||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
use web_sys::Storage;
|
use web_sys::Storage;
|
||||||
|
|
||||||
use crate::{app_state::{AppState, parse_recipes}, js_lib};
|
use crate::{
|
||||||
|
app_state::{parse_recipes, AppState},
|
||||||
|
js_lib,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error(String);
|
pub struct Error(String);
|
||||||
@ -66,8 +69,8 @@ impl From<std::string::FromUtf8Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<reqwasm::Error> for Error {
|
impl From<gloo_net::Error> for Error {
|
||||||
fn from(item: reqwasm::Error) -> Self {
|
fn from(item: gloo_net::Error) -> Self {
|
||||||
Error(format!("{:?}", item))
|
Error(format!("{:?}", item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,8 +97,15 @@ impl LocalStore {
|
|||||||
|
|
||||||
pub fn store_app_state(&self, state: &AppState) {
|
pub fn store_app_state(&self, state: &AppState) {
|
||||||
self.migrate_local_store();
|
self.migrate_local_store();
|
||||||
|
let state = match to_string(state) {
|
||||||
|
Ok(state) => state,
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, ?state, "Error deserializing app_state");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
self.store
|
self.store
|
||||||
.set("app_state", &to_string(state).unwrap())
|
.set("app_state", &state)
|
||||||
.expect("Failed to set our app state");
|
.expect("Failed to set our app state");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +114,8 @@ impl LocalStore {
|
|||||||
self.store.get("app_state").map_or(None, |val| {
|
self.store.get("app_state").map_or(None, |val| {
|
||||||
val.map(|s| {
|
val.map(|s| {
|
||||||
debug!("Found an app_state object");
|
debug!("Found an app_state object");
|
||||||
let mut app_state: AppState = from_str(&s).expect("Failed to deserialize app state");
|
let mut app_state: AppState =
|
||||||
|
from_str(&s).expect("Failed to deserialize app state");
|
||||||
let recipes = parse_recipes(&self.get_recipes()).expect("Failed to parse recipes");
|
let recipes = parse_recipes(&self.get_recipes()).expect("Failed to parse recipes");
|
||||||
if let Some(recipes) = recipes {
|
if let Some(recipes) = recipes {
|
||||||
debug!("Populating recipes");
|
debug!("Populating recipes");
|
||||||
@ -153,12 +164,12 @@ impl LocalStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn migrate_local_store(&self) {
|
fn migrate_local_store(&self) {
|
||||||
for k in self.get_storage_keys()
|
for k in self.get_storage_keys().into_iter().filter(|k| {
|
||||||
.into_iter()
|
k.starts_with("categor") || k == "inventory" || k.starts_with("plan") || k == "staples"
|
||||||
.filter(|k| k.starts_with("categor") || k == "inventory" || k.starts_with("plan") || k == "staples") {
|
}) {
|
||||||
// Deleting old local store key
|
// Deleting old local store key
|
||||||
debug!("Deleting old local store key {}", k);
|
debug!("Deleting old local store key {}", k);
|
||||||
self.store.delete(&k).expect("Failed to delete storage key");
|
self.store.delete(&k).expect("Failed to delete storage key");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,13 +277,17 @@ impl HttpStore {
|
|||||||
debug!("attempting login request against api.");
|
debug!("attempting login request against api.");
|
||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/auth");
|
path.push_str("/auth");
|
||||||
let result = reqwasm::http::Request::get(&path)
|
let request = gloo_net::http::Request::get(&path)
|
||||||
.header(
|
.header(
|
||||||
"Authorization",
|
"authorization",
|
||||||
format!("Basic {}", token68(user, pass)).as_str(),
|
format!("Basic {}", token68(user, pass)).as_str(),
|
||||||
)
|
)
|
||||||
.send()
|
.mode(web_sys::RequestMode::SameOrigin)
|
||||||
.await;
|
.credentials(web_sys::RequestCredentials::SameOrigin)
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build request");
|
||||||
|
debug!(?request, "Sending auth request");
|
||||||
|
let result = request.send().await;
|
||||||
if let Ok(resp) = &result {
|
if let Ok(resp) = &result {
|
||||||
if resp.status() == 200 {
|
if resp.status() == 200 {
|
||||||
let user_data = resp
|
let user_data = resp
|
||||||
@ -294,7 +309,7 @@ impl HttpStore {
|
|||||||
debug!("Retrieving User Account data");
|
debug!("Retrieving User Account data");
|
||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/account");
|
path.push_str("/account");
|
||||||
let result = reqwasm::http::Request::get(&path).send().await;
|
let result = gloo_net::http::Request::get(&path).send().await;
|
||||||
if let Ok(resp) = &result {
|
if let Ok(resp) = &result {
|
||||||
if resp.status() == 200 {
|
if resp.status() == 200 {
|
||||||
let user_data = resp
|
let user_data = resp
|
||||||
@ -315,9 +330,9 @@ impl HttpStore {
|
|||||||
pub async fn fetch_categories(&self) -> Result<Option<Vec<(String, String)>>, Error> {
|
pub async fn fetch_categories(&self) -> Result<Option<Vec<(String, String)>>, Error> {
|
||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/category_map");
|
path.push_str("/category_map");
|
||||||
let resp = match reqwasm::http::Request::get(&path).send().await {
|
let resp = match gloo_net::http::Request::get(&path).send().await {
|
||||||
Ok(resp) => resp,
|
Ok(resp) => resp,
|
||||||
Err(reqwasm::Error::JsError(err)) => {
|
Err(gloo_net::Error::JsError(err)) => {
|
||||||
error!(path, ?err, "Error hitting api");
|
error!(path, ?err, "Error hitting api");
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@ -345,9 +360,9 @@ impl HttpStore {
|
|||||||
pub async fn fetch_recipes(&self) -> Result<Option<Vec<RecipeEntry>>, Error> {
|
pub async fn fetch_recipes(&self) -> Result<Option<Vec<RecipeEntry>>, Error> {
|
||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/recipes");
|
path.push_str("/recipes");
|
||||||
let resp = match reqwasm::http::Request::get(&path).send().await {
|
let resp = match gloo_net::http::Request::get(&path).send().await {
|
||||||
Ok(resp) => resp,
|
Ok(resp) => resp,
|
||||||
Err(reqwasm::Error::JsError(err)) => {
|
Err(gloo_net::Error::JsError(err)) => {
|
||||||
error!(path, ?err, "Error hitting api");
|
error!(path, ?err, "Error hitting api");
|
||||||
return Ok(self.local_store.get_recipes());
|
return Ok(self.local_store.get_recipes());
|
||||||
}
|
}
|
||||||
@ -375,9 +390,9 @@ impl HttpStore {
|
|||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/recipe/");
|
path.push_str("/recipe/");
|
||||||
path.push_str(id.as_ref());
|
path.push_str(id.as_ref());
|
||||||
let resp = match reqwasm::http::Request::get(&path).send().await {
|
let resp = match gloo_net::http::Request::get(&path).send().await {
|
||||||
Ok(resp) => resp,
|
Ok(resp) => resp,
|
||||||
Err(reqwasm::Error::JsError(err)) => {
|
Err(gloo_net::Error::JsError(err)) => {
|
||||||
error!(path, ?err, "Error hitting api");
|
error!(path, ?err, "Error hitting api");
|
||||||
return Ok(self.local_store.get_recipe_entry(id.as_ref()));
|
return Ok(self.local_store.get_recipe_entry(id.as_ref()));
|
||||||
}
|
}
|
||||||
@ -413,7 +428,7 @@ impl HttpStore {
|
|||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/recipe");
|
path.push_str("/recipe");
|
||||||
path.push_str(&format!("/{}", recipe.as_ref()));
|
path.push_str(&format!("/{}", recipe.as_ref()));
|
||||||
let resp = reqwasm::http::Request::delete(&path).send().await?;
|
let resp = gloo_net::http::Request::delete(&path).send().await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
Err(format!("Status: {}", resp.status()).into())
|
Err(format!("Status: {}", resp.status()).into())
|
||||||
} else {
|
} else {
|
||||||
@ -431,10 +446,9 @@ impl HttpStore {
|
|||||||
return Err("Recipe Ids can not be empty".into());
|
return Err("Recipe Ids can not be empty".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let serialized = to_string(&recipes).expect("Unable to serialize recipe entries");
|
let resp = gloo_net::http::Request::post(&path)
|
||||||
let resp = reqwasm::http::Request::post(&path)
|
.json(&recipes)
|
||||||
.body(&serialized)
|
.expect("Failed to set body")
|
||||||
.header("content-type", "application/json")
|
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
@ -449,9 +463,9 @@ impl HttpStore {
|
|||||||
pub async fn store_categories(&self, categories: &Vec<(String, String)>) -> Result<(), Error> {
|
pub async fn store_categories(&self, categories: &Vec<(String, String)>) -> Result<(), Error> {
|
||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/category_map");
|
path.push_str("/category_map");
|
||||||
let resp = reqwasm::http::Request::post(&path)
|
let resp = gloo_net::http::Request::post(&path)
|
||||||
.body(to_string(&categories).expect("Unable to encode categories as json"))
|
.json(&categories)
|
||||||
.header("content-type", "application/json")
|
.expect("Failed to set body")
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
@ -503,9 +517,9 @@ impl HttpStore {
|
|||||||
pub async fn store_plan(&self, plan: Vec<(String, i32)>) -> Result<(), Error> {
|
pub async fn store_plan(&self, plan: Vec<(String, i32)>) -> Result<(), Error> {
|
||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/plan");
|
path.push_str("/plan");
|
||||||
let resp = reqwasm::http::Request::post(&path)
|
let resp = gloo_net::http::Request::post(&path)
|
||||||
.body(to_string(&plan).expect("Unable to encode plan as json"))
|
.json(&plan)
|
||||||
.header("content-type", "application/json")
|
.expect("Failed to set body")
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
@ -525,9 +539,9 @@ impl HttpStore {
|
|||||||
path.push_str("/plan");
|
path.push_str("/plan");
|
||||||
path.push_str("/at");
|
path.push_str("/at");
|
||||||
path.push_str(&format!("/{}", date));
|
path.push_str(&format!("/{}", date));
|
||||||
let resp = reqwasm::http::Request::post(&path)
|
let resp = gloo_net::http::Request::post(&path)
|
||||||
.body(to_string(&plan).expect("Unable to encode plan as json"))
|
.json(&plan)
|
||||||
.header("content-type", "application/json")
|
.expect("Failed to set body")
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
@ -542,7 +556,7 @@ impl HttpStore {
|
|||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/plan");
|
path.push_str("/plan");
|
||||||
path.push_str("/all");
|
path.push_str("/all");
|
||||||
let resp = reqwasm::http::Request::get(&path).send().await?;
|
let resp = gloo_net::http::Request::get(&path).send().await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
Err(format!("Status: {}", resp.status()).into())
|
Err(format!("Status: {}", resp.status()).into())
|
||||||
} else {
|
} else {
|
||||||
@ -561,7 +575,7 @@ impl HttpStore {
|
|||||||
path.push_str("/plan");
|
path.push_str("/plan");
|
||||||
path.push_str("/at");
|
path.push_str("/at");
|
||||||
path.push_str(&format!("/{}", date));
|
path.push_str(&format!("/{}", date));
|
||||||
let resp = reqwasm::http::Request::delete(&path).send().await?;
|
let resp = gloo_net::http::Request::delete(&path).send().await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
Err(format!("Status: {}", resp.status()).into())
|
Err(format!("Status: {}", resp.status()).into())
|
||||||
} else {
|
} else {
|
||||||
@ -577,7 +591,7 @@ impl HttpStore {
|
|||||||
path.push_str("/plan");
|
path.push_str("/plan");
|
||||||
path.push_str("/at");
|
path.push_str("/at");
|
||||||
path.push_str(&format!("/{}", date));
|
path.push_str(&format!("/{}", date));
|
||||||
let resp = reqwasm::http::Request::get(&path).send().await?;
|
let resp = gloo_net::http::Request::get(&path).send().await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
Err(format!("Status: {}", resp.status()).into())
|
Err(format!("Status: {}", resp.status()).into())
|
||||||
} else {
|
} else {
|
||||||
@ -594,7 +608,7 @@ impl HttpStore {
|
|||||||
//pub async fn fetch_plan(&self) -> Result<Option<Vec<(String, i32)>>, Error> {
|
//pub async fn fetch_plan(&self) -> Result<Option<Vec<(String, i32)>>, Error> {
|
||||||
// let mut path = self.v2_path();
|
// let mut path = self.v2_path();
|
||||||
// path.push_str("/plan");
|
// path.push_str("/plan");
|
||||||
// let resp = reqwasm::http::Request::get(&path).send().await?;
|
// let resp = gloo_net::http::Request::get(&path).send().await?;
|
||||||
// if resp.status() != 200 {
|
// if resp.status() != 200 {
|
||||||
// Err(format!("Status: {}", resp.status()).into())
|
// Err(format!("Status: {}", resp.status()).into())
|
||||||
// } else {
|
// } else {
|
||||||
@ -623,7 +637,7 @@ impl HttpStore {
|
|||||||
path.push_str("/inventory");
|
path.push_str("/inventory");
|
||||||
path.push_str("/at");
|
path.push_str("/at");
|
||||||
path.push_str(&format!("/{}", date));
|
path.push_str(&format!("/{}", date));
|
||||||
let resp = reqwasm::http::Request::get(&path).send().await?;
|
let resp = gloo_net::http::Request::get(&path).send().await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
Err(format!("Status: {}", resp.status()).into())
|
Err(format!("Status: {}", resp.status()).into())
|
||||||
} else {
|
} else {
|
||||||
@ -658,7 +672,7 @@ impl HttpStore {
|
|||||||
> {
|
> {
|
||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/inventory");
|
path.push_str("/inventory");
|
||||||
let resp = reqwasm::http::Request::get(&path).send().await?;
|
let resp = gloo_net::http::Request::get(&path).send().await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
Err(format!("Status: {}", resp.status()).into())
|
Err(format!("Status: {}", resp.status()).into())
|
||||||
} else {
|
} else {
|
||||||
@ -695,13 +709,10 @@ impl HttpStore {
|
|||||||
path.push_str(&format!("/{}", date));
|
path.push_str(&format!("/{}", date));
|
||||||
let filtered_ingredients: Vec<IngredientKey> = filtered_ingredients.into_iter().collect();
|
let filtered_ingredients: Vec<IngredientKey> = filtered_ingredients.into_iter().collect();
|
||||||
let modified_amts: Vec<(IngredientKey, String)> = modified_amts.into_iter().collect();
|
let modified_amts: Vec<(IngredientKey, String)> = modified_amts.into_iter().collect();
|
||||||
debug!("Storing inventory data in cache");
|
|
||||||
let serialized_inventory = to_string(&(filtered_ingredients, modified_amts, extra_items))
|
|
||||||
.expect("Unable to encode plan as json");
|
|
||||||
debug!("Storing inventory data via API");
|
debug!("Storing inventory data via API");
|
||||||
let resp = reqwasm::http::Request::post(&path)
|
let resp = gloo_net::http::Request::post(&path)
|
||||||
.body(&serialized_inventory)
|
.json(&(filtered_ingredients, modified_amts, extra_items))
|
||||||
.header("content-type", "application/json")
|
.expect("Failed to set body")
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
@ -724,13 +735,10 @@ impl HttpStore {
|
|||||||
path.push_str("/inventory");
|
path.push_str("/inventory");
|
||||||
let filtered_ingredients: Vec<IngredientKey> = filtered_ingredients.into_iter().collect();
|
let filtered_ingredients: Vec<IngredientKey> = filtered_ingredients.into_iter().collect();
|
||||||
let modified_amts: Vec<(IngredientKey, String)> = modified_amts.into_iter().collect();
|
let modified_amts: Vec<(IngredientKey, String)> = modified_amts.into_iter().collect();
|
||||||
debug!("Storing inventory data in cache");
|
|
||||||
let serialized_inventory = to_string(&(filtered_ingredients, modified_amts, extra_items))
|
|
||||||
.expect("Unable to encode plan as json");
|
|
||||||
debug!("Storing inventory data via API");
|
debug!("Storing inventory data via API");
|
||||||
let resp = reqwasm::http::Request::post(&path)
|
let resp = gloo_net::http::Request::post(&path)
|
||||||
.body(&serialized_inventory)
|
.json(&(filtered_ingredients, modified_amts, extra_items))
|
||||||
.header("content-type", "application/json")
|
.expect("Failed to set body")
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
@ -745,7 +753,7 @@ impl HttpStore {
|
|||||||
pub async fn fetch_staples(&self) -> Result<Option<String>, Error> {
|
pub async fn fetch_staples(&self) -> Result<Option<String>, Error> {
|
||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/staples");
|
path.push_str("/staples");
|
||||||
let resp = reqwasm::http::Request::get(&path).send().await?;
|
let resp = gloo_net::http::Request::get(&path).send().await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
debug!("Invalid response back");
|
debug!("Invalid response back");
|
||||||
Err(format!("Status: {}", resp.status()).into())
|
Err(format!("Status: {}", resp.status()).into())
|
||||||
@ -759,15 +767,15 @@ impl HttpStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn store_staples<S: AsRef<str>>(&self, content: S) -> Result<(), Error> {
|
pub async fn store_staples<S: AsRef<str> + serde::Serialize>(
|
||||||
|
&self,
|
||||||
|
content: S,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/staples");
|
path.push_str("/staples");
|
||||||
let serialized_staples: String =
|
let resp = gloo_net::http::Request::post(&path)
|
||||||
to_string(content.as_ref()).expect("Failed to serialize staples to json");
|
.json(&content)
|
||||||
|
.expect("Failed to set body")
|
||||||
let resp = reqwasm::http::Request::post(&path)
|
|
||||||
.body(&serialized_staples)
|
|
||||||
.header("content-type", "application/json")
|
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
|
@ -174,8 +174,9 @@ impl StateMachine {
|
|||||||
local_store: &LocalStore,
|
local_store: &LocalStore,
|
||||||
original: &Signal<AppState>,
|
original: &Signal<AppState>,
|
||||||
) -> Result<(), crate::api::Error> {
|
) -> Result<(), crate::api::Error> {
|
||||||
// TODO(jwall): We use a linear Signal in here to ensure that we only
|
// NOTE(jwall): We use a linear Signal in here to ensure that we only
|
||||||
// call set on the signal once.
|
// call set on the signal once. When the LinearSignal get's dropped it
|
||||||
|
// will call set on the contained Signal.
|
||||||
let mut original: LinearSignal<AppState> = original.into();
|
let mut original: LinearSignal<AppState> = original.into();
|
||||||
if let Some(state) = local_store.fetch_app_state() {
|
if let Some(state) = local_store.fetch_app_state() {
|
||||||
original = original.update(state);
|
original = original.update(state);
|
||||||
|
@ -49,7 +49,7 @@ fn CategoryRow<'ctx, G: Html>(cx: Scope<'ctx>, props: CategoryRowProps<'ctx>) ->
|
|||||||
});
|
});
|
||||||
view! {cx,
|
view! {cx,
|
||||||
tr() {
|
tr() {
|
||||||
td() {
|
td(class="margin-bot-1 border-bottom") {
|
||||||
(ingredient_clone) br()
|
(ingredient_clone) br()
|
||||||
Indexed(
|
Indexed(
|
||||||
iterable=recipes,
|
iterable=recipes,
|
||||||
|
@ -17,8 +17,8 @@ use sycamore::prelude::*;
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn Footer<G: Html>(cx: Scope) -> View<G> {
|
pub fn Footer<G: Html>(cx: Scope) -> View<G> {
|
||||||
view! {cx,
|
view! {cx,
|
||||||
nav(class="no-print") {
|
nav(class="no-print menu-font") {
|
||||||
ul {
|
ul(class="no-list") {
|
||||||
li { a(href="https://github.com/zaphar/kitchen") { "On Github" } }
|
li { a(href="https://github.com/zaphar/kitchen") { "On Github" } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,9 @@ pub fn Header<'ctx, G: Html>(cx: Scope<'ctx>, h: StateHandler<'ctx>) -> View<G>
|
|||||||
None => "Login".to_owned(),
|
None => "Login".to_owned(),
|
||||||
});
|
});
|
||||||
view! {cx,
|
view! {cx,
|
||||||
nav(class="no-print") {
|
nav(class="no-print row-flex align-center header-bg heavy-bottom-border menu-font") {
|
||||||
h1(class="title") { "Kitchen" }
|
h1(class="title") { "Kitchen" }
|
||||||
ul {
|
ul(class="row-flex align-center no-list") {
|
||||||
li { a(href="/ui/planning/select") { "MealPlan" } }
|
li { a(href="/ui/planning/select") { "MealPlan" } }
|
||||||
li { a(href="/ui/manage/ingredients") { "Manage" } }
|
li { a(href="/ui/manage/ingredients") { "Manage" } }
|
||||||
li { a(href="/ui/login") { (login.get()) } }
|
li { a(href="/ui/login") { (login.get()) } }
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
use maud::html;
|
use maud::html;
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
use wasm_web_component::{web_component, WebComponentBinding};
|
use wasm_web_component::{web_component, WebComponentBinding};
|
||||||
use web_sys::{CustomEvent, Event, HtmlElement, InputEvent, ShadowRoot, window};
|
use web_sys::{window, CustomEvent, CustomEventInit, Event, HtmlElement, InputEvent, ShadowRoot};
|
||||||
|
|
||||||
use crate::js_lib::LogFailures;
|
use crate::js_lib::LogFailures;
|
||||||
|
|
||||||
@ -135,11 +135,10 @@ impl WebComponentBinding for NumberSpinner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let mut eventDict = CustomEventInit::new();
|
||||||
|
eventDict.detail(&JsValue::from_f64(self.value as f64));
|
||||||
element
|
element
|
||||||
.set_attribute("val", &format!("{}", self.value))
|
.dispatch_event(&CustomEvent::new_with_event_init_dict("updated", &eventDict).unwrap())
|
||||||
.swallow_and_log();
|
|
||||||
element
|
|
||||||
.dispatch_event(&CustomEvent::new("updated").unwrap())
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
debug!("Dispatched updated event");
|
debug!("Dispatched updated event");
|
||||||
}
|
}
|
||||||
@ -147,13 +146,18 @@ impl WebComponentBinding for NumberSpinner {
|
|||||||
fn attribute_changed_mut(
|
fn attribute_changed_mut(
|
||||||
&mut self,
|
&mut self,
|
||||||
_element: &web_sys::HtmlElement,
|
_element: &web_sys::HtmlElement,
|
||||||
name: wasm_bindgen::JsValue,
|
name: JsValue,
|
||||||
old_value: wasm_bindgen::JsValue,
|
old_value: JsValue,
|
||||||
new_value: wasm_bindgen::JsValue,
|
new_value: JsValue,
|
||||||
) {
|
) {
|
||||||
let nval_el = self.get_input_el();
|
let nval_el = self.get_input_el();
|
||||||
let name = name.as_string().unwrap();
|
let name = name.as_string().unwrap();
|
||||||
debug!(?name, ?old_value, ?new_value, "COUNTS: handling attribute change");
|
debug!(
|
||||||
|
?name,
|
||||||
|
?old_value,
|
||||||
|
?new_value,
|
||||||
|
"COUNTS: handling attribute change"
|
||||||
|
);
|
||||||
match name.as_str() {
|
match name.as_str() {
|
||||||
"val" => {
|
"val" => {
|
||||||
debug!("COUNTS: got an updated value");
|
debug!("COUNTS: got an updated value");
|
||||||
@ -208,9 +212,10 @@ impl WebComponentBinding for NumberSpinner {
|
|||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
pub struct NumberProps<'ctx, F>
|
pub struct NumberProps<'ctx, F>
|
||||||
where
|
where
|
||||||
F: Fn(Event),
|
F: Fn(CustomEvent),
|
||||||
{
|
{
|
||||||
name: String,
|
name: String,
|
||||||
|
class: String,
|
||||||
on_change: Option<F>,
|
on_change: Option<F>,
|
||||||
min: f64,
|
min: f64,
|
||||||
counter: &'ctx Signal<f64>,
|
counter: &'ctx Signal<f64>,
|
||||||
@ -219,10 +224,11 @@ where
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn NumberField<'ctx, F, G: Html>(cx: Scope<'ctx>, props: NumberProps<'ctx, F>) -> View<G>
|
pub fn NumberField<'ctx, F, G: Html>(cx: Scope<'ctx>, props: NumberProps<'ctx, F>) -> View<G>
|
||||||
where
|
where
|
||||||
F: Fn(web_sys::Event) + 'ctx,
|
F: Fn(CustomEvent) + 'ctx,
|
||||||
{
|
{
|
||||||
let NumberProps {
|
let NumberProps {
|
||||||
name,
|
name,
|
||||||
|
class,
|
||||||
on_change,
|
on_change,
|
||||||
min,
|
min,
|
||||||
counter,
|
counter,
|
||||||
@ -231,21 +237,13 @@ where
|
|||||||
// TODO(jwall): I'm pretty sure this triggers: https://github.com/sycamore-rs/sycamore/issues/602
|
// TODO(jwall): I'm pretty sure this triggers: https://github.com/sycamore-rs/sycamore/issues/602
|
||||||
// Which means I probably have to wait till v0.9.0 drops or switch to leptos.
|
// Which means I probably have to wait till v0.9.0 drops or switch to leptos.
|
||||||
let id = name.clone();
|
let id = name.clone();
|
||||||
create_effect(cx, move || {
|
let initial_count = *counter.get();
|
||||||
let new_count = *counter.get();
|
|
||||||
debug!(new_count, "COUNTS: Updating spinner with new value");
|
|
||||||
if let Some(el) = window().unwrap().document().unwrap().get_element_by_id(id.as_str()) {
|
|
||||||
debug!("COUNTS: found element");
|
|
||||||
el.set_attribute("val", new_count.to_string().as_str()).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let id = name.clone();
|
|
||||||
view! {cx,
|
view! {cx,
|
||||||
number-spinner(id=id, val=*counter.get(), min=min, on:updated=move |evt: Event| {
|
number-spinner(id=id, class=(class), val=(initial_count), min=min, on:updated=move |evt: Event| {
|
||||||
let target: HtmlElement = evt.target().unwrap().dyn_into().unwrap();
|
let event = evt.unchecked_into::<CustomEvent>();
|
||||||
let val: f64 = target.get_attribute("val").unwrap().parse().unwrap();
|
let val: f64 = event.detail().as_f64().unwrap();
|
||||||
counter.set(val);
|
counter.set(val);
|
||||||
on_change.as_ref().map(|f| f(evt));
|
on_change.as_ref().map(|f| f(event));
|
||||||
debug!(counter=%(counter.get_untracked()), "set counter to new value");
|
debug!(counter=%(counter.get_untracked()), "set counter to new value");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use chrono::NaiveDate;
|
|
||||||
// Copyright 2023 Jeremy Wall (Jeremy@marzhilsltudios.com)
|
// Copyright 2023 Jeremy Wall (Jeremy@marzhilsltudios.com)
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -12,6 +11,7 @@ use chrono::NaiveDate;
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
use chrono::NaiveDate;
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
use crate::app_state::{Message, StateHandler};
|
use crate::app_state::{Message, StateHandler};
|
||||||
@ -23,30 +23,25 @@ pub struct PlanListProps<'ctx> {
|
|||||||
list: &'ctx ReadSignal<Vec<NaiveDate>>,
|
list: &'ctx ReadSignal<Vec<NaiveDate>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jwall): We also need a "new plan button"
|
|
||||||
#[instrument(skip_all, fields(dates=?props.list))]
|
#[instrument(skip_all, fields(dates=?props.list))]
|
||||||
#[component]
|
#[component]
|
||||||
pub fn PlanList<'ctx, G: Html>(cx: Scope<'ctx>, props: PlanListProps<'ctx>) -> View<G> {
|
pub fn PlanList<'ctx, G: Html>(cx: Scope<'ctx>, props: PlanListProps<'ctx>) -> View<G> {
|
||||||
let PlanListProps { sh, list } = props;
|
let PlanListProps { sh, list } = props;
|
||||||
view! {cx,
|
view! {cx,
|
||||||
div() {
|
div() {
|
||||||
table() {
|
div(class="column-flex") {
|
||||||
Indexed(
|
Indexed(
|
||||||
iterable=list,
|
iterable=list,
|
||||||
view=move |cx, date| {
|
view=move |cx, date| {
|
||||||
let date_display = format!("{}", date);
|
let date_display = format!("{}", date);
|
||||||
view!{cx,
|
view!{cx,
|
||||||
tr() {
|
div(class="row-flex margin-bot-half") {
|
||||||
td() {
|
button(class="outline margin-right-1", on:click=move |_| {
|
||||||
span(role="button", class="outline", on:click=move |_| {
|
sh.dispatch(cx, Message::SelectPlanDate(date, None))
|
||||||
sh.dispatch(cx, Message::SelectPlanDate(date, None))
|
}) { (date_display) }
|
||||||
}) { (date_display) }
|
button(class="destructive", on:click=move |_| {
|
||||||
}
|
sh.dispatch(cx, Message::DeletePlan(date, None))
|
||||||
td() {
|
}) { "Delete Plan" }
|
||||||
span(role="button", class="destructive", on:click=move |_| {
|
|
||||||
sh.dispatch(cx, Message::DeletePlan(date, None))
|
|
||||||
}) { "Delete Plan" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -79,12 +79,14 @@ pub fn Editor<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>)
|
|||||||
|
|
||||||
debug!("creating editor view");
|
debug!("creating editor view");
|
||||||
view! {cx,
|
view! {cx,
|
||||||
label(for="recipe_category") { "Category" }
|
div {
|
||||||
input(name="recipe_category", bind:value=category, on:change=move |_| dirty.set(true))
|
label(for="recipe_category") { "Category" }
|
||||||
div(class="grid") {
|
input(name="recipe_category", bind:value=category, on:change=move |_| dirty.set(true))
|
||||||
div {
|
}
|
||||||
label(for="recipe_text") { "Recipe" }
|
div {
|
||||||
textarea(name="recipe_text", bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
div(class="row-flex") {
|
||||||
|
label(for="recipe_text", class="block align-stretch expand-height") { "Recipe: " }
|
||||||
|
textarea(class="width-third", name="recipe_text", bind:value=text, aria-invalid=aria_hint.get(), cols="50", rows=20, on:change=move |_| {
|
||||||
dirty.set(true);
|
dirty.set(true);
|
||||||
check_recipe_parses(text.get_untracked().as_str(), error_text, aria_hint);
|
check_recipe_parses(text.get_untracked().as_str(), error_text, aria_hint);
|
||||||
}, on:input=move |_| {
|
}, on:input=move |_| {
|
||||||
@ -97,34 +99,36 @@ pub fn Editor<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>)
|
|||||||
}
|
}
|
||||||
div(class="parse") { (error_text.get()) }
|
div(class="parse") { (error_text.get()) }
|
||||||
}
|
}
|
||||||
span(role="button", on:click=move |_| {
|
div {
|
||||||
let unparsed = text.get_untracked();
|
button(on:click=move |_| {
|
||||||
if check_recipe_parses(unparsed.as_str(), error_text, aria_hint) {
|
let unparsed = text.get_untracked();
|
||||||
debug!("triggering a save");
|
if check_recipe_parses(unparsed.as_str(), error_text, aria_hint) {
|
||||||
if !*dirty.get_untracked() {
|
debug!("triggering a save");
|
||||||
debug!("Recipe text is unchanged");
|
if !*dirty.get_untracked() {
|
||||||
return;
|
debug!("Recipe text is unchanged");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debug!("Recipe text is changed");
|
||||||
|
let category = category.get_untracked();
|
||||||
|
let category = if category.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(category.as_ref().clone())
|
||||||
|
};
|
||||||
|
let recipe_entry = RecipeEntry(
|
||||||
|
id.get_untracked().as_ref().clone(),
|
||||||
|
text.get_untracked().as_ref().clone(),
|
||||||
|
category,
|
||||||
|
);
|
||||||
|
sh.dispatch(cx, Message::SaveRecipe(recipe_entry, None));
|
||||||
|
dirty.set(false);
|
||||||
}
|
}
|
||||||
debug!("Recipe text is changed");
|
// TODO(jwall): Show error message if trying to save when recipe doesn't parse.
|
||||||
let category = category.get_untracked();
|
}) { "Save" } " "
|
||||||
let category = if category.is_empty() {
|
button(on:click=move |_| {
|
||||||
None
|
sh.dispatch(cx, Message::RemoveRecipe(id.get_untracked().as_ref().to_owned(), Some(Box::new(|| sycamore_router::navigate("/ui/planning/plan")))));
|
||||||
} else {
|
}) { "delete" } " "
|
||||||
Some(category.as_ref().clone())
|
}
|
||||||
};
|
|
||||||
let recipe_entry = RecipeEntry(
|
|
||||||
id.get_untracked().as_ref().clone(),
|
|
||||||
text.get_untracked().as_ref().clone(),
|
|
||||||
category,
|
|
||||||
);
|
|
||||||
sh.dispatch(cx, Message::SaveRecipe(recipe_entry, None));
|
|
||||||
dirty.set(false);
|
|
||||||
}
|
|
||||||
// TODO(jwall): Show error message if trying to save when recipe doesn't parse.
|
|
||||||
}) { "Save" } " "
|
|
||||||
span(role="button", on:click=move |_| {
|
|
||||||
sh.dispatch(cx, Message::RemoveRecipe(id.get_untracked().as_ref().to_owned(), Some(Box::new(|| sycamore_router::navigate("/ui/planning/plan")))));
|
|
||||||
}) { "delete" } " "
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +146,7 @@ fn Steps<G: Html>(cx: Scope, steps: Vec<recipes::Step>) -> View<G> {
|
|||||||
view! {cx,
|
view! {cx,
|
||||||
div {
|
div {
|
||||||
h3 { "Step " (idx + 1) }
|
h3 { "Step " (idx + 1) }
|
||||||
ul(class="ingredients") {
|
ul(class="ingredients no-list") {
|
||||||
(ingredient_fragments)
|
(ingredient_fragments)
|
||||||
}
|
}
|
||||||
div(class="instructions") {
|
div(class="instructions") {
|
||||||
|
@ -52,7 +52,7 @@ pub fn CategoryGroup<'ctx, G: Html>(
|
|||||||
});
|
});
|
||||||
view! {cx,
|
view! {cx,
|
||||||
h2 { (category) }
|
h2 { (category) }
|
||||||
div(class="recipe_selector no-print") {
|
div(class="no-print row-flex flex-wrap-start align-stretch") {
|
||||||
(View::new_fragment(
|
(View::new_fragment(
|
||||||
rows.get().iter().cloned().map(|r| {
|
rows.get().iter().cloned().map(|r| {
|
||||||
view ! {cx,
|
view ! {cx,
|
||||||
@ -61,7 +61,7 @@ pub fn CategoryGroup<'ctx, G: Html>(
|
|||||||
view=move |cx, sig| {
|
view=move |cx, sig| {
|
||||||
let title = create_memo(cx, move || sig.get().1.title.clone());
|
let title = create_memo(cx, move || sig.get().1.title.clone());
|
||||||
view! {cx,
|
view! {cx,
|
||||||
div(class="cell") { RecipeSelection(i=sig.get().0.to_owned(), title=title, sh=sh) }
|
div(class="cell column-flex justify-end align-stretch") { RecipeSelection(i=sig.get().0.to_owned(), title=title, sh=sh) }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key=|sig| sig.get().0.to_owned(),
|
key=|sig| sig.get().0.to_owned(),
|
||||||
@ -108,13 +108,13 @@ pub fn RecipePlan<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> Vie
|
|||||||
},
|
},
|
||||||
key=|(ref cat, _)| cat.clone(),
|
key=|(ref cat, _)| cat.clone(),
|
||||||
)
|
)
|
||||||
span(role="button", on:click=move |_| {
|
button(on:click=move |_| {
|
||||||
sh.dispatch(cx, Message::LoadState(None));
|
sh.dispatch(cx, Message::LoadState(None));
|
||||||
}) { "Reset" } " "
|
}) { "Reset" } " "
|
||||||
span(role="button", on:click=move |_| {
|
button(on:click=move |_| {
|
||||||
sh.dispatch(cx, Message::ResetRecipeCounts);
|
sh.dispatch(cx, Message::ResetRecipeCounts);
|
||||||
}) { "Clear All" } " "
|
}) { "Clear All" } " "
|
||||||
span(role="button", on:click=move |_| {
|
button(on:click=move |_| {
|
||||||
// Poor man's click event signaling.
|
// Poor man's click event signaling.
|
||||||
sh.dispatch(cx, Message::SaveState(None));
|
sh.dispatch(cx, Message::SaveState(None));
|
||||||
}) { "Save Plan" } " "
|
}) { "Save Plan" } " "
|
||||||
|
@ -65,8 +65,8 @@ pub fn RecipeSelection<'ctx, G: Html>(
|
|||||||
let name = format!("recipe_id:{}", id);
|
let name = format!("recipe_id:{}", id);
|
||||||
let for_id = name.clone();
|
let for_id = name.clone();
|
||||||
view! {cx,
|
view! {cx,
|
||||||
label(for=for_id) { a(href=href) { (*title) } }
|
label(for=for_id, class="flex-item-grow") { a(href=href) { (*title) } }
|
||||||
NumberField(name=name, counter=count, min=0.0, on_change=Some(move |_| {
|
NumberField(name=name, class="flex-item-shrink".to_string(), counter=count, min=0.0, on_change=Some(move |_| {
|
||||||
debug!(idx=%id, count=%(*count.get_untracked()), "setting recipe count");
|
debug!(idx=%id, count=%(*count.get_untracked()), "setting recipe count");
|
||||||
sh.dispatch(cx, Message::UpdateRecipeCount(id.as_ref().clone(), *count.get_untracked() as usize));
|
sh.dispatch(cx, Message::UpdateRecipeCount(id.as_ref().clone(), *count.get_untracked() as usize));
|
||||||
}))
|
}))
|
||||||
|
@ -109,12 +109,12 @@ fn make_ingredients_rows<'ctx, G: Html>(
|
|||||||
view! {cx,
|
view! {cx,
|
||||||
tr {
|
tr {
|
||||||
td {
|
td {
|
||||||
input(bind:value=amt_signal, type="text", on:change=move |_| {
|
input(bind:value=amt_signal, class="width-5", type="text", on:change=move |_| {
|
||||||
sh.dispatch(cx, Message::UpdateAmt(k_clone.clone(), amt_signal.get_untracked().as_ref().clone()));
|
sh.dispatch(cx, Message::UpdateAmt(k_clone.clone(), amt_signal.get_untracked().as_ref().clone()));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
input(type="button", class="no-print destructive", value="X", on:click={
|
input(type="button", class="fit-content no-print destructive", value="X", on:click={
|
||||||
move |_| {
|
move |_| {
|
||||||
sh.dispatch(cx, Message::AddFilteredIngredient(k.clone()));
|
sh.dispatch(cx, Message::AddFilteredIngredient(k.clone()));
|
||||||
}})
|
}})
|
||||||
@ -143,14 +143,14 @@ fn make_extras_rows<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> V
|
|||||||
view! {cx,
|
view! {cx,
|
||||||
tr {
|
tr {
|
||||||
td {
|
td {
|
||||||
input(bind:value=amt_signal, type="text", on:change=move |_| {
|
input(bind:value=amt_signal, class="width-5", type="text", on:change=move |_| {
|
||||||
sh.dispatch(cx, Message::UpdateExtra(idx,
|
sh.dispatch(cx, Message::UpdateExtra(idx,
|
||||||
amt_signal.get_untracked().as_ref().clone(),
|
amt_signal.get_untracked().as_ref().clone(),
|
||||||
name_signal.get_untracked().as_ref().clone()));
|
name_signal.get_untracked().as_ref().clone()));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
input(type="button", class="no-print destructive", value="X", on:click=move |_| {
|
input(type="button", class="fit-content no-print destructive", value="X", on:click=move |_| {
|
||||||
sh.dispatch(cx, Message::RemoveExtra(idx));
|
sh.dispatch(cx, Message::RemoveExtra(idx));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -194,9 +194,7 @@ fn make_shopping_table<'ctx, G: Html>(
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ShoppingList<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
pub fn ShoppingList<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
||||||
let show_staples = sh.get_selector(cx, |state| {
|
let show_staples = sh.get_selector(cx, |state| state.get().use_staples);
|
||||||
state.get().use_staples
|
|
||||||
});
|
|
||||||
view! {cx,
|
view! {cx,
|
||||||
h1 { "Shopping List " }
|
h1 { "Shopping List " }
|
||||||
label(for="show_staples_cb") { "Show staples" }
|
label(for="show_staples_cb") { "Show staples" }
|
||||||
@ -205,15 +203,15 @@ pub fn ShoppingList<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> V
|
|||||||
sh.dispatch(cx, Message::UpdateUseStaples(value));
|
sh.dispatch(cx, Message::UpdateUseStaples(value));
|
||||||
})
|
})
|
||||||
(make_shopping_table(cx, sh, show_staples))
|
(make_shopping_table(cx, sh, show_staples))
|
||||||
span(role="button", class="no-print", on:click=move |_| {
|
button(class="no-print", on:click=move |_| {
|
||||||
info!("Registering add item request for inventory");
|
info!("Registering add item request for inventory");
|
||||||
sh.dispatch(cx, Message::AddExtra(String::new(), String::new()));
|
sh.dispatch(cx, Message::AddExtra(String::new(), String::new()));
|
||||||
}) { "Add Item" } " "
|
}) { "Add Item" } " "
|
||||||
span(role="button", class="no-print", on:click=move |_| {
|
button(class="no-print", on:click=move |_| {
|
||||||
info!("Registering reset request for inventory");
|
info!("Registering reset request for inventory");
|
||||||
sh.dispatch(cx, Message::ResetInventory);
|
sh.dispatch(cx, Message::ResetInventory);
|
||||||
}) { "Reset" } " "
|
}) { "Reset" } " "
|
||||||
span(role="button", class="no-print", on:click=move |_| {
|
button(class="no-print", on:click=move |_| {
|
||||||
info!("Registering save request for inventory");
|
info!("Registering save request for inventory");
|
||||||
sh.dispatch(cx, Message::SaveState(None));
|
sh.dispatch(cx, Message::SaveState(None));
|
||||||
}) { "Save" } " "
|
}) { "Save" } " "
|
||||||
|
@ -72,8 +72,8 @@ pub fn IngredientsEditor<'ctx, G: Html>(
|
|||||||
|
|
||||||
debug!("creating editor view");
|
debug!("creating editor view");
|
||||||
view! {cx,
|
view! {cx,
|
||||||
div(class="grid") {
|
div {
|
||||||
textarea(bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
textarea(class="width-third", bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
||||||
dirty.set(true);
|
dirty.set(true);
|
||||||
}, on:input=move |_| {
|
}, on:input=move |_| {
|
||||||
let current_ts = js_lib::get_ms_timestamp();
|
let current_ts = js_lib::get_ms_timestamp();
|
||||||
@ -84,7 +84,7 @@ pub fn IngredientsEditor<'ctx, G: Html>(
|
|||||||
})
|
})
|
||||||
div(class="parse") { (error_text.get()) }
|
div(class="parse") { (error_text.get()) }
|
||||||
}
|
}
|
||||||
span(role="button", on:click=move |_| {
|
button(on:click=move |_| {
|
||||||
let unparsed = text.get();
|
let unparsed = text.get();
|
||||||
if !*dirty.get_untracked() {
|
if !*dirty.get_untracked() {
|
||||||
debug!("Staples text is unchanged");
|
debug!("Staples text is unchanged");
|
||||||
|
@ -47,12 +47,12 @@ pub fn TabbedView<'a, G: Html>(cx: Scope<'a>, state: TabState<'a, G>) -> View<G>
|
|||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
view! {cx,
|
view! {cx,
|
||||||
nav {
|
nav(class="menu-bg menu-font-2 flex-item-shrink") {
|
||||||
ul(class="tabs") {
|
ul(class="tabs pad-left no-list row-flex align-center") {
|
||||||
(menu)
|
(menu)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
main(class=".conatiner-fluid") {
|
main(class="flex-item-grow content-font") {
|
||||||
(children)
|
(children)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,8 @@ use tracing::error;
|
|||||||
use web_sys::{window, Storage, Window};
|
use web_sys::{window, Storage, Window};
|
||||||
|
|
||||||
pub fn get_storage() -> Storage {
|
pub fn get_storage() -> Storage {
|
||||||
get_window().local_storage()
|
get_window()
|
||||||
|
.local_storage()
|
||||||
.expect("Failed to get storage")
|
.expect("Failed to get storage")
|
||||||
.expect("No storage available")
|
.expect("No storage available")
|
||||||
}
|
}
|
||||||
@ -26,8 +27,7 @@ pub fn get_ms_timestamp() -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_window() -> Window {
|
pub fn get_window() -> Window {
|
||||||
window()
|
window().expect("No window present")
|
||||||
.expect("No window present")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait LogFailures<V, E> {
|
pub trait LogFailures<V, E> {
|
||||||
|
@ -15,10 +15,10 @@ mod api;
|
|||||||
mod app_state;
|
mod app_state;
|
||||||
mod components;
|
mod components;
|
||||||
mod js_lib;
|
mod js_lib;
|
||||||
|
mod linear;
|
||||||
mod pages;
|
mod pages;
|
||||||
mod routing;
|
mod routing;
|
||||||
mod web;
|
mod web;
|
||||||
mod linear;
|
|
||||||
|
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
@ -24,7 +24,10 @@ pub struct LinearSignal<'ctx, Payload> {
|
|||||||
|
|
||||||
impl<'ctx, Payload> Into<LinearSignal<'ctx, Payload>> for &'ctx Signal<Payload> {
|
impl<'ctx, Payload> Into<LinearSignal<'ctx, Payload>> for &'ctx Signal<Payload> {
|
||||||
fn into(self) -> LinearSignal<'ctx, Payload> {
|
fn into(self) -> LinearSignal<'ctx, Payload> {
|
||||||
LinearSignal { signal: self, nv: None }
|
LinearSignal {
|
||||||
|
signal: self,
|
||||||
|
nv: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,9 +27,13 @@ pub fn LoginForm<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View
|
|||||||
input(type="text", id="username", bind:value=username)
|
input(type="text", id="username", bind:value=username)
|
||||||
label(for="password") { "Password" }
|
label(for="password") { "Password" }
|
||||||
input(type="password", bind:value=password)
|
input(type="password", bind:value=password)
|
||||||
span(role="button", on:click=move |_| {
|
button(on:click=move |evt: web_sys::Event| {
|
||||||
info!("Attempting login request");
|
info!("Attempting login request");
|
||||||
let (username, password) = ((*username.get_untracked()).clone(), (*password.get_untracked()).clone());
|
let (username, password) = ((*username.get_untracked()).clone(), (*password.get_untracked()).clone());
|
||||||
|
// NOTE(jwall): This is required if we want to keep the below auth request from
|
||||||
|
// failing to send with blocked by browser. This is because it's on a click and
|
||||||
|
// the form tries to do a submit event and aborts our network request.
|
||||||
|
evt.prevent_default();
|
||||||
if username != "" && password != "" {
|
if username != "" && password != "" {
|
||||||
spawn_local_scoped(cx, async move {
|
spawn_local_scoped(cx, async move {
|
||||||
let store = crate::api::HttpStore::get_from_context(cx);
|
let store = crate::api::HttpStore::get_from_context(cx);
|
||||||
|
@ -18,9 +18,13 @@ use crate::{app_state::StateHandler, components::recipe_list::*};
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn CookPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
pub fn CookPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
||||||
|
let current_plan = sh.get_selector(cx, |state| {
|
||||||
|
state.get().selected_plan_date
|
||||||
|
});
|
||||||
view! {cx,
|
view! {cx,
|
||||||
PlanningPage(
|
PlanningPage(
|
||||||
selected=Some("Cook".to_owned()),
|
selected=Some("Cook".to_owned()),
|
||||||
|
plan_date = current_plan,
|
||||||
) { RecipeList(sh) }
|
) { RecipeList(sh) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,13 @@ use crate::{app_state::StateHandler, components::shopping_list::*};
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn InventoryPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
pub fn InventoryPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
||||||
|
let current_plan = sh.get_selector(cx, |state| {
|
||||||
|
state.get().selected_plan_date
|
||||||
|
});
|
||||||
view! {cx,
|
view! {cx,
|
||||||
PlanningPage(
|
PlanningPage(
|
||||||
selected=Some("Inventory".to_owned()),
|
selected=Some("Inventory".to_owned()),
|
||||||
|
plan_date = current_plan,
|
||||||
) { ShoppingList(sh) }
|
) { ShoppingList(sh) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use crate::components::tabs::*;
|
use crate::components::tabs::*;
|
||||||
|
use chrono::NaiveDate;
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
pub mod cook;
|
pub mod cook;
|
||||||
@ -25,14 +26,19 @@ pub use plan::*;
|
|||||||
pub use select::*;
|
pub use select::*;
|
||||||
|
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
pub struct PageState<'a, G: Html> {
|
pub struct PageState<'ctx, G: Html> {
|
||||||
pub children: Children<'a, G>,
|
pub children: Children<'ctx, G>,
|
||||||
pub selected: Option<String>,
|
pub selected: Option<String>,
|
||||||
|
pub plan_date: &'ctx ReadSignal<Option<NaiveDate>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn PlanningPage<'a, G: Html>(cx: Scope<'a>, state: PageState<'a, G>) -> View<G> {
|
pub fn PlanningPage<'ctx, G: Html>(cx: Scope<'ctx>, state: PageState<'ctx, G>) -> View<G> {
|
||||||
let PageState { children, selected } = state;
|
let PageState {
|
||||||
|
children,
|
||||||
|
selected,
|
||||||
|
plan_date,
|
||||||
|
} = state;
|
||||||
let children = children.call(cx);
|
let children = children.call(cx);
|
||||||
let planning_tabs: Vec<(String, &'static str)> = vec![
|
let planning_tabs: Vec<(String, &'static str)> = vec![
|
||||||
("/ui/planning/select".to_owned(), "Select"),
|
("/ui/planning/select".to_owned(), "Select"),
|
||||||
@ -45,6 +51,10 @@ pub fn PlanningPage<'a, G: Html>(cx: Scope<'a>, state: PageState<'a, G>) -> View
|
|||||||
TabbedView(
|
TabbedView(
|
||||||
selected=selected,
|
selected=selected,
|
||||||
tablist=planning_tabs,
|
tablist=planning_tabs,
|
||||||
) { (children) }
|
) { div {
|
||||||
|
"Plan Date: " (plan_date.get().map_or(String::from("Unknown"), |d| format!("{}", d)))
|
||||||
|
}
|
||||||
|
(children)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,13 @@ use sycamore::prelude::*;
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn PlanPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
pub fn PlanPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
||||||
|
let current_plan = sh.get_selector(cx, |state| {
|
||||||
|
state.get().selected_plan_date
|
||||||
|
});
|
||||||
view! {cx,
|
view! {cx,
|
||||||
PlanningPage(
|
PlanningPage(
|
||||||
selected=Some("Plan".to_owned()),
|
selected=Some("Plan".to_owned()),
|
||||||
|
plan_date = current_plan,
|
||||||
) { RecipePlan(sh) }
|
) { RecipePlan(sh) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,12 +32,16 @@ pub fn SelectPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> Vie
|
|||||||
plans.sort_unstable_by(|d1, d2| d2.cmp(d1));
|
plans.sort_unstable_by(|d1, d2| d2.cmp(d1));
|
||||||
plans
|
plans
|
||||||
});
|
});
|
||||||
|
let current_plan = sh.get_selector(cx, |state| {
|
||||||
|
state.get().selected_plan_date
|
||||||
|
});
|
||||||
view! {cx,
|
view! {cx,
|
||||||
PlanningPage(
|
PlanningPage(
|
||||||
selected=Some("Select".to_owned()),
|
selected=Some("Select".to_owned()),
|
||||||
|
plan_date = current_plan.clone(),
|
||||||
) {
|
) {
|
||||||
PlanList(sh=sh, list=plan_dates)
|
PlanList(sh=sh, list=plan_dates)
|
||||||
span(role="button", on:click=move |_| {
|
button(on:click=move |_| {
|
||||||
sh.dispatch(cx, Message::SelectPlanDate(chrono::offset::Local::now().naive_local().date(), Some(Box::new(|| {
|
sh.dispatch(cx, Message::SelectPlanDate(chrono::offset::Local::now().naive_local().date(), Some(Box::new(|| {
|
||||||
sycamore_router::navigate("/ui/planning/plan");
|
sycamore_router::navigate("/ui/planning/plan");
|
||||||
}))))
|
}))))
|
||||||
|
@ -136,11 +136,10 @@ pub fn Handler<'ctx, G: Html>(cx: Scope<'ctx>, props: HandlerProps<'ctx>) -> Vie
|
|||||||
integration=HistoryIntegration::new(),
|
integration=HistoryIntegration::new(),
|
||||||
view=move |cx: Scope, route: &ReadSignal<Routes>| {
|
view=move |cx: Scope, route: &ReadSignal<Routes>| {
|
||||||
view!{cx,
|
view!{cx,
|
||||||
div(class="app") {
|
div(class="column-flex") {
|
||||||
Header(sh)
|
Header(sh)
|
||||||
(route_switch(route.get().as_ref(), cx, sh))
|
(route_switch(route.get().as_ref(), cx, sh))
|
||||||
Footer { }
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
||||||
use tracing::{info, debug, instrument};
|
use tracing::{debug, info, instrument};
|
||||||
|
|
||||||
use crate::app_state::Message;
|
use crate::app_state::Message;
|
||||||
use crate::{api, routing::Handler as RouteHandler};
|
use crate::{api, routing::Handler as RouteHandler};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2022 Jeremy Wall
|
* Copyright 2023 Jeremy Wall
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,12 +21,34 @@
|
|||||||
--unicode-button-size: 2em;
|
--unicode-button-size: 2em;
|
||||||
--toast-anim-duration: 3s;
|
--toast-anim-duration: 3s;
|
||||||
--notification-font-size: calc(var(--font-size) / 2);
|
--notification-font-size: calc(var(--font-size) / 2);
|
||||||
--error-message-color: rgba(255, 98, 0, 0.797);
|
--error-message-color: #CD5C08;
|
||||||
--error-message-bg: grey;
|
--error-message-bg: grey;
|
||||||
--border-width: 2px;
|
--border-width: 3px;
|
||||||
--cell-margin: 1em;
|
--cell-margin: 1em;
|
||||||
|
--nav-margin: 2em;
|
||||||
|
--main-color: #A9907E;
|
||||||
|
--light-accent: #F3DEBA;
|
||||||
|
--dark-accent: #ABC4AA;
|
||||||
|
--heavy-accent: #675D50;
|
||||||
|
--text-color: black;
|
||||||
|
--menu-bg: var(--main-color);
|
||||||
|
--header-bg: var(--light-accent);
|
||||||
|
--font-size: 1.5rem;
|
||||||
|
--menu-font-size: 2em;
|
||||||
|
--cell-target: 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** TODO(jwall): Dark color scheme?
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--text-color: white;
|
||||||
|
--menu-bg: var(--main-color);
|
||||||
|
--header-bg: var(--dark-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
**/
|
||||||
|
|
||||||
|
/** TODO(jwall): Seperate these out into composable classes **/
|
||||||
@media print {
|
@media print {
|
||||||
|
|
||||||
.no-print,
|
.no-print,
|
||||||
@ -39,28 +61,138 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
/** Resets **/
|
||||||
:root {
|
|
||||||
--font-size: 35px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--tab-border-color: lightgrey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
padding: 10px;
|
margin: 0px;
|
||||||
margin: 10px;
|
padding: 0px;
|
||||||
|
background-color: var(--header-bg);
|
||||||
|
font-size: var(--font-size)
|
||||||
}
|
}
|
||||||
|
|
||||||
nav>ul.tabs>li {
|
body * {
|
||||||
border-style: none;
|
color: black;
|
||||||
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav>ul.tabs>li.selected {
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** layout classes **/
|
||||||
|
|
||||||
|
.column-flex {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-flex {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-item-grow {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-item-shrink {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-wrap-start {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-height {
|
||||||
|
height: 100%;
|
||||||
|
min-height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-stretch {
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-third {
|
||||||
|
min-width: fit-content;
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-list {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fit-content {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-10 {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-5 {
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-bottom {
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-color: var(--tab-border-color);
|
||||||
|
border-bottom-width: var(--tab-border-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-bot-1 {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-bot-half {
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-right-1 {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Typography classes **/
|
||||||
|
|
||||||
|
.menu-font {
|
||||||
|
font-size: var(--menu-font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-font-2 {
|
||||||
|
font-size: calc(var(--menu-font-size) / 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-font {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** element specific styling **/
|
||||||
|
nav li {
|
||||||
|
margin-right: var(--nav-margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** color and borders **/
|
||||||
|
.header-bg {
|
||||||
|
background-color: var(--header-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.heavy-bottom-border {
|
||||||
|
border-bottom: var(--border-width) solid var(--heavy-accent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Situational **/
|
||||||
|
.selected {
|
||||||
border-style: none;
|
border-style: none;
|
||||||
border-bottom-style: var(--tab-border-style);
|
border-bottom-style: var(--tab-border-style);
|
||||||
border-bottom-color: var(--tab-border-color);
|
border-bottom-color: var(--tab-border-color);
|
||||||
@ -74,10 +206,40 @@ nav>h1 {
|
|||||||
display: inline;
|
display: inline;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
border-bottom-left-radius: 1em;
|
||||||
|
padding: 1em;
|
||||||
|
width: 100%;
|
||||||
|
overflow-block: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
margin: 1em;
|
||||||
|
width: var(--cell-target);
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-end {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-bg {
|
||||||
|
background-color: var(--menu-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-left {
|
||||||
|
padding-left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app nav li {
|
||||||
|
margin-bottom: var(--nav-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
.destructive {
|
.destructive {
|
||||||
background-color: firebrick !important;
|
background-color: #CD5C08 !important;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-count-inc-dec {
|
.item-count-inc-dec {
|
||||||
@ -129,24 +291,3 @@ nav>h1 {
|
|||||||
opacity: 0
|
opacity: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.recipe_selector {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
align-content: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recipe_selector .cell {
|
|
||||||
margin: 1em;
|
|
||||||
width: calc(100% / 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cell {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: stretch;
|
|
||||||
align-content: stretch;
|
|
||||||
}
|
|
||||||
|
349
web/static/normalize.css
vendored
Normal file
349
web/static/normalize.css
vendored
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||||
|
|
||||||
|
/* Document
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the line height in all browsers.
|
||||||
|
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
html {
|
||||||
|
line-height: 1.15; /* 1 */
|
||||||
|
-webkit-text-size-adjust: 100%; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sections
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the margin in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the `main` element consistently in IE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the font size and margin on `h1` elements within `section` and
|
||||||
|
* `article` contexts in Chrome, Firefox, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin: 0.67em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grouping content
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Add the correct box sizing in Firefox.
|
||||||
|
* 2. Show the overflow in Edge and IE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
box-sizing: content-box; /* 1 */
|
||||||
|
height: 0; /* 1 */
|
||||||
|
overflow: visible; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||||
|
* 2. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-family: monospace, monospace; /* 1 */
|
||||||
|
font-size: 1em; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text-level semantics
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the gray background on active links in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Remove the bottom border in Chrome 57-
|
||||||
|
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
border-bottom: none; /* 1 */
|
||||||
|
text-decoration: underline; /* 2 */
|
||||||
|
text-decoration: underline dotted; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||||
|
* 2. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: monospace, monospace; /* 1 */
|
||||||
|
font-size: 1em; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||||
|
* all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Embedded content
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the border on images inside links in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Change the font styles in all browsers.
|
||||||
|
* 2. Remove the margin in Firefox and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit; /* 1 */
|
||||||
|
font-size: 100%; /* 1 */
|
||||||
|
line-height: 1.15; /* 1 */
|
||||||
|
margin: 0; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the overflow in IE.
|
||||||
|
* 1. Show the overflow in Edge.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input { /* 1 */
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||||
|
* 1. Remove the inheritance of text transform in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
select { /* 1 */
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type="button"],
|
||||||
|
[type="reset"],
|
||||||
|
[type="submit"] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inner border and padding in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
[type="button"]::-moz-focus-inner,
|
||||||
|
[type="reset"]::-moz-focus-inner,
|
||||||
|
[type="submit"]::-moz-focus-inner {
|
||||||
|
border-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the focus styles unset by the previous rule.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button:-moz-focusring,
|
||||||
|
[type="button"]:-moz-focusring,
|
||||||
|
[type="reset"]:-moz-focusring,
|
||||||
|
[type="submit"]:-moz-focusring {
|
||||||
|
outline: 1px dotted ButtonText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the padding in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
padding: 0.35em 0.75em 0.625em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the text wrapping in Edge and IE.
|
||||||
|
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||||
|
* 3. Remove the padding so developers are not caught out when they zero out
|
||||||
|
* `fieldset` elements in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
legend {
|
||||||
|
box-sizing: border-box; /* 1 */
|
||||||
|
color: inherit; /* 2 */
|
||||||
|
display: table; /* 1 */
|
||||||
|
max-width: 100%; /* 1 */
|
||||||
|
padding: 0; /* 3 */
|
||||||
|
white-space: normal; /* 1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||||
|
*/
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the default vertical scrollbar in IE 10+.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Add the correct box sizing in IE 10.
|
||||||
|
* 2. Remove the padding in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="checkbox"],
|
||||||
|
[type="radio"] {
|
||||||
|
box-sizing: border-box; /* 1 */
|
||||||
|
padding: 0; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="number"]::-webkit-inner-spin-button,
|
||||||
|
[type="number"]::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the odd appearance in Chrome and Safari.
|
||||||
|
* 2. Correct the outline style in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="search"] {
|
||||||
|
-webkit-appearance: textfield; /* 1 */
|
||||||
|
outline-offset: -2px; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inner padding in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
* 2. Change font properties to `inherit` in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button; /* 1 */
|
||||||
|
font: inherit; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interactive
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
details {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the correct display in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Misc
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 10+.
|
||||||
|
*/
|
||||||
|
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user