diff --git a/kiosk/Cargo.lock b/kiosk/Cargo.lock new file mode 100644 index 0000000..e53bfaf --- /dev/null +++ b/kiosk/Cargo.lock @@ -0,0 +1,2821 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atomic_refcell" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e4227379beff4205943696e6c3e0cd809bacdf3f0edd6e3dd153e2269571a4" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "betterframe-kiosk" +version = "0.1.0" +dependencies = [ + "dirs", + "gst-plugin-gtk4", + "gstreamer", + "gstreamer-video", + "gtk4", + "hostname", + "reqwest", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cairo-rs" +version = "0.20.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e3bd0f4e25afa9cabc157908d14eeef9067d6448c49414d17b3fb55f0eadd0" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "glib", + "libc", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059cc746549898cbfd9a47754288e5a958756650ef4652bbb6c5f71a6bda4f8b" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-expr" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd242894c084f4beed508a56952750bce3e96e85eb68fdc153637daa163e10c" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b34f3b580c988bd217e9543a2de59823fafae369d1a055555e5f95a8b130b96" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk4" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk4-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk4-sys" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdk4-win32" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54beb3e19bff7ccc05153e043bf0eea9cf76c07a3387fb0151bfa0fbfce9df0" +dependencies = [ + "gdk4", + "gdk4-win32-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk4-win32-sys" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554341f26b2f6191aaf2ecb4d0089f3696a40cadb2dba75dad03e48a6dc0eb9" +dependencies = [ + "gdk4-sys", + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gio" +version = "0.20.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e27e276e7b6b8d50f6376ee7769a71133e80d093bdc363bd0af71664228b831" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "pin-project-lite", + "smallvec", +] + +[[package]] +name = "gio-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "windows-sys 0.59.0", +] + +[[package]] +name = "glib" +version = "0.20.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc4b6e352d4716d84d7dde562dd9aee2a7d48beb872dd9ece7f2d1515b2d683" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-macros" +version = "0.20.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8084af62f09475a3f529b1629c10c429d7600ee1398ae12dd3bf175d74e7145" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gobject-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "graphene-rs" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b86dfad7d14251c9acaf1de63bc8754b7e3b4e5b16777b6f5a748208fe9519b" +dependencies = [ + "glib", + "graphene-sys", + "libc", +] + +[[package]] +name = "graphene-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df583a85ba2d5e15e1797e40d666057b28bc2f60a67c9c24145e6db2cc3861ea" +dependencies = [ + "glib-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gsk4" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855" +dependencies = [ + "cairo-rs", + "gdk4", + "glib", + "graphene-rs", + "gsk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gsk4-sys" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc" +dependencies = [ + "cairo-sys-rs", + "gdk4-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gst-plugin-gtk4" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4cb890d0df1814d2f61ad7d5a4db8ec4fed0e2ac287af26d3c342be1dfae75b" +dependencies = [ + "async-channel", + "gdk4-win32", + "gst-plugin-version-helper", + "gstreamer", + "gstreamer-base", + "gstreamer-gl", + "gstreamer-video", + "gtk4", + "once_cell", + "windows-sys 0.60.2", +] + +[[package]] +name = "gst-plugin-version-helper" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68a894ef2d738054b950e1dbef5d9012b63fd968d4d32dbccd31bd8d8d4b219" +dependencies = [ + "chrono", + "toml_edit 0.23.10+spec-1.0.0", +] + +[[package]] +name = "gstreamer" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8757a87f3706560037a01a9f06a59fcc7bdb0864744dcf73546606e60c4316e1" +dependencies = [ + "cfg-if", + "futures-channel", + "futures-core", + "futures-util", + "glib", + "gstreamer-sys", + "itertools", + "libc", + "muldiv", + "num-integer", + "num-rational", + "once_cell", + "option-operations", + "paste", + "pin-project-lite", + "smallvec", + "thiserror", +] + +[[package]] +name = "gstreamer-base" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f19a74fd04ffdcb847dd322640f2cf520897129d00a7bcb92fd62a63f3e27404" +dependencies = [ + "atomic_refcell", + "cfg-if", + "glib", + "gstreamer", + "gstreamer-base-sys", + "libc", +] + +[[package]] +name = "gstreamer-base-sys" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f2fb0037b6d3c5b51f60dea11e667910f33be222308ca5a101450018a09840" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-gl" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c5fe4cfc154d1aef2d5bddaf45c8fcb80af5ae4416795650a01c38e01d6d5bd" +dependencies = [ + "glib", + "gstreamer", + "gstreamer-base", + "gstreamer-gl-sys", + "gstreamer-video", + "libc", + "once_cell", +] + +[[package]] +name = "gstreamer-gl-sys" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a832c21d4522ed5e1b8dfc676a45361969216b144fc03af413a38c471f38bcf7" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-base-sys", + "gstreamer-sys", + "gstreamer-video-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-sys" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feea73b4d92dbf9c24a203c9cd0bcc740d584f6b5960d5faf359febf288919b2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-video" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1318b599d77ca4f7702ecbdeac1672d6304cb16b7e5752fabb3ee8260449a666" +dependencies = [ + "cfg-if", + "futures-channel", + "glib", + "gstreamer", + "gstreamer-base", + "gstreamer-video-sys", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "gstreamer-video-sys" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a70f0947f12d253b9de9bc3fd92f981e4d025336c18389c7f08cdf388a99f5c" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-base-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk4" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f274dd0102c21c47bbfa8ebcb92d0464fab794a22fad6c3f3d5f165139a326d6" +dependencies = [ + "cairo-rs", + "field-offset", + "futures-channel", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "graphene-rs", + "gsk4", + "gtk4-macros", + "gtk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gtk4-macros" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gtk4-sys" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "gsk4-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "muldiv" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl" +version = "0.10.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "option-operations" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0" +dependencies = [ + "paste", +] + +[[package]] +name = "pango" +version = "0.20.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6576b311f6df659397043a5fa8a021da8f72e34af180b44f7d57348de691ab5c" +dependencies = [ + "gio", + "glib", + "libc", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186909673fc09be354555c302c0b3dcf753cd9fa08dcb8077fa663c80fb243fa" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.11+spec-1.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "7.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.2", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.2", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.2", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/kiosk/src/bundle.rs b/kiosk/src/bundle.rs index 62f3e5d..b3398fb 100644 --- a/kiosk/src/bundle.rs +++ b/kiosk/src/bundle.rs @@ -25,7 +25,6 @@ pub struct BundleDisplay { pub struct BundleLayout { pub id: u32, pub name: String, - pub regions: Vec, pub grid_cols: u32, pub grid_rows: u32, pub priority: String, @@ -37,19 +36,11 @@ pub struct BundleLayout { } #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct BundleRegion { - pub name: String, +pub struct BundleCell { pub row: u32, pub col: u32, - #[serde(rename = "rowSpan")] pub row_span: u32, - #[serde(rename = "colSpan")] pub col_span: u32, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct BundleCell { - pub region_name: String, pub content_type: String, pub camera_id: Option, pub stream_selector: Option, diff --git a/kiosk/src/ui.rs b/kiosk/src/ui.rs index 8f71728..a8e3767 100644 --- a/kiosk/src/ui.rs +++ b/kiosk/src/ui.rs @@ -120,8 +120,8 @@ fn render_bundle(window: &ApplicationWindow, bundle: KioskBundle) { return; }; - if layout.regions.is_empty() { - warn!("layout has no regions"); + if layout.cells.is_empty() { + warn!("layout has no cells"); show_logo(window); return; } @@ -141,12 +141,6 @@ fn render_bundle(window: &ApplicationWindow, bundle: KioskBundle) { let pipelines: Rc>> = Rc::new(RefCell::new(Vec::new())); for cell in &layout.cells { - let region = layout.regions.iter().find(|r| r.name == cell.region_name); - let Some(region) = region else { - warn!("region '{}' not found in layout", cell.region_name); - continue; - }; - let widget: gtk::Widget = match cell.content_type.as_str() { "camera" => { if let Some(cam_id) = cell.camera_id { @@ -192,30 +186,13 @@ fn render_bundle(window: &ApplicationWindow, bundle: KioskBundle) { grid.attach( &widget, - region.col as i32, - region.row as i32, - region.col_span as i32, - region.row_span as i32, + cell.col as i32, + cell.row as i32, + cell.col_span as i32, + cell.row_span as i32, ); } - // Fill empty regions - for region in &layout.regions { - if !layout.cells.iter().any(|c| c.region_name == region.name) { - let empty = GtkBox::new(Orientation::Vertical, 0); - add_css(&empty, "box { background-color: #111; }"); - empty.set_vexpand(true); - empty.set_hexpand(true); - grid.attach( - &empty, - region.col as i32, - region.row as i32, - region.col_span as i32, - region.row_span as i32, - ); - } - } - window.set_child(Some(&grid)); let pipelines_ref = pipelines.clone(); diff --git a/server/src/plugins/service-admin-http/routes-admin.ts b/server/src/plugins/service-admin-http/routes-admin.ts index 8be0a3c..c71496f 100644 --- a/server/src/plugins/service-admin-http/routes-admin.ts +++ b/server/src/plugins/service-admin-http/routes-admin.ts @@ -1,7 +1,7 @@ /** * Admin page routes — overview, cameras, kiosks, labels, etc. */ -import { type H3, readBody, getRouterParam } from "h3"; +import { type H3, readBody, getRouterParam, getQuery } from "h3"; import { htmlPage } from "./html-response.js"; import type { AdminDeps } from "./index.js"; import { confirmPairing } from "../../shared/pairing.js"; @@ -19,7 +19,6 @@ import { DisplaysPage, DisplayEditPage, } from "../../web-templates/admin-pages.js"; -import type { Display } from "../../shared/types.js"; function sanitizeRtspUrl(raw: string): string { const match = raw.match(/^(rtsp:\/\/)([^@]+)@(.+)$/); @@ -198,105 +197,36 @@ export function registerAdminRoutes(app: H3, deps: AdminDeps): void { app.get("/admin/layouts", (event) => { const user = event.context.user!; const layouts = deps.repo.listLayouts(); - const displayIds = [...new Set(layouts.map((l) => l.display_id))]; - const displays = new Map(); - for (const did of displayIds) { - const d = deps.repo.getDisplayById(did); - if (d) displays.set(did, d); + // For each layout, how many displays use it (for the list view). + const displayCounts = new Map(); + for (const l of layouts) { + displayCounts.set(l.id, deps.repo.listDisplaysForLayout(l.id).length); } - return htmlPage(LayoutsPage({ user: user.username, layouts, displays })); + return htmlPage(LayoutsPage({ user: user.username, layouts, displayCounts })); }); app.get("/admin/layouts/new", (event) => { const user = event.context.user!; - return htmlPage(LayoutNewPage({ - user: user.username, - displays: deps.repo.listDisplays(), - })); + return htmlPage(LayoutNewPage({ user: user.username })); }); app.post("/admin/layouts/new", async (event) => { const user = event.context.user!; const body = await readBody>(event); const name = (body?.["name"] ?? "").trim(); - const preset = body?.["preset"] ?? "custom"; - const displayId = parseInt(body?.["display_id"] ?? "", 10); const priority = body?.["priority"] ?? "normal"; const description = (body?.["description"] ?? "").trim() || null; - const isDefault = body?.["is_default"] === "1"; const resetsIdleTimer = body?.["resets_idle_timer"] === "1"; const errors: string[] = []; if (!name || name.length > 128) errors.push("Name required (max 128 chars)."); - if (isNaN(displayId)) errors.push("Select a display."); - - type Region = { name: string; row: number; col: number; rowSpan: number; colSpan: number }; - let regions: Region[] = []; - let gridCols = 1; - let gridRows = 1; - - if (preset === "fullscreen") { - gridCols = 1; - gridRows = 1; - regions = [{ name: "main", row: 0, col: 0, rowSpan: 1, colSpan: 1 }]; - } else if (preset === "2x2") { - gridCols = 2; - gridRows = 2; - regions = [ - { name: "tl", row: 0, col: 0, rowSpan: 1, colSpan: 1 }, - { name: "tr", row: 0, col: 1, rowSpan: 1, colSpan: 1 }, - { name: "bl", row: 1, col: 0, rowSpan: 1, colSpan: 1 }, - { name: "br", row: 1, col: 1, rowSpan: 1, colSpan: 1 }, - ]; - } else if (preset === "1plus3") { - gridCols = 2; - gridRows = 3; - regions = [ - { name: "main", row: 0, col: 0, rowSpan: 3, colSpan: 1 }, - { name: "r1", row: 0, col: 1, rowSpan: 1, colSpan: 1 }, - { name: "r2", row: 1, col: 1, rowSpan: 1, colSpan: 1 }, - { name: "r3", row: 2, col: 1, rowSpan: 1, colSpan: 1 }, - ]; - } else if (preset === "3x3") { - gridCols = 3; - gridRows = 3; - regions = [ - { name: "r1", row: 0, col: 0, rowSpan: 1, colSpan: 1 }, - { name: "r2", row: 0, col: 1, rowSpan: 1, colSpan: 1 }, - { name: "r3", row: 0, col: 2, rowSpan: 1, colSpan: 1 }, - { name: "r4", row: 1, col: 0, rowSpan: 1, colSpan: 1 }, - { name: "r5", row: 1, col: 1, rowSpan: 1, colSpan: 1 }, - { name: "r6", row: 1, col: 2, rowSpan: 1, colSpan: 1 }, - { name: "r7", row: 2, col: 0, rowSpan: 1, colSpan: 1 }, - { name: "r8", row: 2, col: 1, rowSpan: 1, colSpan: 1 }, - { name: "r9", row: 2, col: 2, rowSpan: 1, colSpan: 1 }, - ]; - } else { - // Custom - gridCols = parseInt(body?.["grid_cols"] ?? "1", 10); - gridRows = parseInt(body?.["grid_rows"] ?? "1", 10); - if (isNaN(gridCols) || gridCols < 1 || gridCols > 12) errors.push("Grid columns must be 1-12."); - if (isNaN(gridRows) || gridRows < 1 || gridRows > 12) errors.push("Grid rows must be 1-12."); - - const regionsStr = (body?.["regions"] ?? "").trim(); - if (!regionsStr) { - errors.push("Regions JSON is required for custom layout."); - } else { - try { - regions = JSON.parse(regionsStr); - if (!Array.isArray(regions) || regions.length === 0) { - errors.push("Regions must be a non-empty JSON array."); - } - } catch { - errors.push("Invalid JSON in regions field."); - } - } + if (priority !== "hot" && priority !== "normal" && priority !== "cold") { + errors.push("Priority must be hot/normal/cold."); } if (errors.length > 0) { return htmlPage(LayoutNewPage({ user: user.username, - displays: deps.repo.listDisplays(), error: errors.join(" "), values: body, })); @@ -305,12 +235,7 @@ export function registerAdminRoutes(app: H3, deps: AdminDeps): void { const layout = deps.repo.createLayout({ name, description, - regions, - grid_cols: gridCols, - grid_rows: gridRows, - display_id: displayId, priority, - is_default: isDefault, resets_idle_timer: resetsIdleTimer, }); @@ -322,16 +247,19 @@ export function registerAdminRoutes(app: H3, deps: AdminDeps): void { const id = Number(getRouterParam(event, "id")); const layout = deps.repo.getLayoutById(id); if (!layout) return new Response(null, { status: 302, headers: { location: "/admin/layouts" } }); - const display = deps.repo.getDisplayById(layout.display_id); - if (!display) return new Response(null, { status: 302, headers: { location: "/admin/layouts" } }); const cells = deps.repo.layoutCells(id); const cameras = deps.repo.listCameras(); + const displays = deps.repo.listDisplaysForLayout(id); + const q = getQuery(event) as Record; + const selectedRaw = q["cell"]; + const selectedCellId = selectedRaw ? Number(selectedRaw) : null; return htmlPage(LayoutEditPage({ user: user.username, layout, - display, + displays, cells, cameras, + selectedCellId: selectedCellId && cells.some((c) => c.id === selectedCellId) ? selectedCellId : null, })); }); @@ -345,28 +273,93 @@ export function registerAdminRoutes(app: H3, deps: AdminDeps): void { description: body?.["description"] || null, priority: (body?.["priority"] ?? "normal") as any, cooling_timeout_seconds: coolingTimeout, - is_default: body?.["is_default"] === "1", resets_idle_timer: body?.["resets_idle_timer"] === "1", }); return new Response(null, { status: 302, headers: { location: `/admin/layouts/${id}` } }); }); + // Create a new 1x1 cell. Two body shapes: + // { position: { row, col } } — explicit position, may shift others. + // { after_cell_id, direction } — relative to existing cell (right/below/left/above). + // Returns 302 redirect to the layout edit page (htmx will swap on hx-target). app.post("/admin/layouts/:id/cells", async (event) => { const layoutId = Number(getRouterParam(event, "id")); - const body = await readBody>(event); - const regionName = (body?.["region_name"] ?? "").trim(); - const contentType = body?.["content_type"] ?? "camera"; + const body = await readBody>(event); - if (!regionName) { - return new Response(null, { status: 302, headers: { location: `/admin/layouts/${layoutId}` } }); + let row = 0; + let col = 0; + + const afterCellIdRaw = body?.["after_cell_id"]; + const direction = typeof body?.["direction"] === "string" ? (body["direction"] as string) : ""; + + if (afterCellIdRaw && direction) { + const afterId = Number(afterCellIdRaw); + const cells = deps.repo.layoutCells(layoutId); + const ref = cells.find((c) => c.id === afterId); + if (!ref) { + return new Response(null, { status: 302, headers: { location: `/admin/layouts/${layoutId}` } }); + } + if (direction === "right") { + row = ref.row; + col = ref.col + ref.col_span; + } else if (direction === "bottom") { + row = ref.row + ref.row_span; + col = ref.col; + } else if (direction === "left") { + row = ref.row; + if (ref.col === 0) { + deps.repo.shiftCellsForLayout(layoutId, "col", 0, 1); + col = 0; + } else { + col = ref.col - 1; + } + } else if (direction === "above") { + col = ref.col; + if (ref.row === 0) { + deps.repo.shiftCellsForLayout(layoutId, "row", 0, 1); + row = 0; + } else { + row = ref.row - 1; + } + } + } else { + // Explicit position — accept top-level row/col or nested position. + const pos = body?.["position"]; + if (pos && typeof pos === "object" && !Array.isArray(pos)) { + row = Number((pos as { row: number; col: number }).row) || 0; + col = Number((pos as { row: number; col: number }).col) || 0; + } else { + row = Number(body?.["row"] ?? 0) || 0; + col = Number(body?.["col"] ?? 0) || 0; + } } deps.repo.createLayoutCell({ layout_id: layoutId, - region_name: regionName, + row, + col, + row_span: 1, + col_span: 1, + content_type: "html", + html_content: null, + }); + + return new Response(null, { status: 302, headers: { location: `/admin/layouts/${layoutId}` } }); + }); + + // Update a cell's content assignment. + app.post("/admin/layouts/:id/cells/:cellId", async (event) => { + const layoutId = Number(getRouterParam(event, "id")); + const cellId = Number(getRouterParam(event, "cellId")); + const body = await readBody>(event); + const contentType = (body?.["content_type"] ?? "html") as "camera" | "web" | "html"; + + deps.repo.updateLayoutCell(cellId, { content_type: contentType, camera_id: contentType === "camera" && body?.["camera_id"] ? Number(body["camera_id"]) : null, - stream_selector: contentType === "camera" ? (body?.["stream_selector"] ?? "auto") : null, + stream_selector: contentType === "camera" + ? ((body?.["stream_selector"] as "auto" | "main" | "sub") ?? "auto") + : "auto", web_url: contentType === "web" ? (body?.["web_url"] ?? null) : null, html_content: contentType === "html" ? (body?.["html_content"] ?? null) : null, }); @@ -400,26 +393,64 @@ export function registerAdminRoutes(app: H3, deps: AdminDeps): void { const id = Number(getRouterParam(event, "id")); const display = deps.repo.getDisplayById(id); if (!display) return new Response(null, { status: 302, headers: { location: "/admin/displays" } }); - const layouts = deps.repo.layoutsForDisplay(id); + const attachedLayouts = deps.repo.listLayoutsForDisplay(id); + const attachedIds = new Set(attachedLayouts.map((l) => l.id)); + const availableLayouts = deps.repo.listLayouts().filter((l) => !attachedIds.has(l.id)); const kiosk = display.kiosk_id ? deps.repo.getKioskById(display.kiosk_id) : null; - return htmlPage(DisplayEditPage({ user: user.username, display, layouts, kioskName: kiosk?.name ?? null })); + return htmlPage(DisplayEditPage({ + user: user.username, + display, + attachedLayouts, + availableLayouts, + kioskName: kiosk?.name ?? null, + })); }); app.post("/admin/displays/:id", async (event) => { const id = Number(getRouterParam(event, "id")); const body = await readBody>(event); - const defaultLayoutId = body?.["default_layout_id"] ? Number(body["default_layout_id"]) : null; + const defaultLayoutIdRaw = body?.["default_layout_id"]; + const defaultLayoutId = defaultLayoutIdRaw ? Number(defaultLayoutIdRaw) : null; + + // Validate default_layout_id is actually attached to this display. + let validatedDefault: number | null = defaultLayoutId; + if (defaultLayoutId != null) { + const attached = deps.repo.listLayoutsForDisplay(id); + if (!attached.some((l) => l.id === defaultLayoutId)) { + validatedDefault = null; + } + } + + // width/height are no longer admin-editable — they come from the kiosk's + // hardware report. Just update the editable fields. deps.repo.updateDisplay(id, { name: body?.["name"], - default_layout_id: defaultLayoutId, + default_layout_id: validatedDefault, idle_timeout_seconds: parseInt(body?.["idle_timeout_seconds"] ?? "0", 10), sleep_timeout_seconds: parseInt(body?.["sleep_timeout_seconds"] ?? "0", 10), - width_px: body?.["width_px"] ? parseInt(body["width_px"], 10) : undefined, - height_px: body?.["height_px"] ? parseInt(body["height_px"], 10) : undefined, } as any); return new Response(null, { status: 302, headers: { location: `/admin/displays/${id}` } }); }); + // Attach a layout to a display. + app.post("/admin/displays/:id/layouts", async (event) => { + const displayId = Number(getRouterParam(event, "id")); + const body = await readBody>(event); + const layoutId = body?.["layout_id"] ? Number(body["layout_id"]) : null; + if (layoutId && Number.isFinite(layoutId)) { + deps.repo.attachLayoutToDisplay(displayId, layoutId); + } + return new Response(null, { status: 302, headers: { location: `/admin/displays/${displayId}` } }); + }); + + // Detach a layout from a display. + app.post("/admin/displays/:id/layouts/:layoutId/remove", (event) => { + const displayId = Number(getRouterParam(event, "id")); + const layoutId = Number(getRouterParam(event, "layoutId")); + deps.repo.detachLayoutFromDisplay(displayId, layoutId); + return new Response(null, { status: 302, headers: { location: `/admin/displays/${displayId}` } }); + }); + app.get("/admin/labels", (event) => { const user = event.context.user!; return htmlPage(LabelsPage({ user: user.username, labels: deps.repo.listLabels() })); diff --git a/server/src/plugins/service-store/mappers.ts b/server/src/plugins/service-store/mappers.ts index 1fe89dc..710b48e 100644 --- a/server/src/plugins/service-store/mappers.ts +++ b/server/src/plugins/service-store/mappers.ts @@ -180,7 +180,7 @@ export function rowToLayout(r: Row): Layout { regions: j(r["regions"], []), grid_cols: n(r["grid_cols"]) || 1, grid_rows: n(r["grid_rows"]) || 1, - display_id: n(r["display_id"]), + display_id: nn(r["display_id"]), priority: s(r["priority"]) as LayoutPriority, cooling_timeout_seconds: nn(r["cooling_timeout_seconds"]), preload_camera_ids: j(r["preload_camera_ids"], []), @@ -194,6 +194,10 @@ export function rowToLayoutCell(r: Row): LayoutCell { id: n(r["id"]), layout_id: n(r["layout_id"]), region_name: s(r["region_name"]), + row: n(r["row"]), + col: n(r["col"]), + row_span: n(r["row_span"]) || 1, + col_span: n(r["col_span"]) || 1, content_type: s(r["content_type"]) as CellContentType, camera_id: nn(r["camera_id"]), stream_selector: s(r["stream_selector"]) as StreamSelector, diff --git a/server/src/plugins/service-store/migrations.ts b/server/src/plugins/service-store/migrations.ts index d80c7f3..5ffe174 100644 --- a/server/src/plugins/service-store/migrations.ts +++ b/server/src/plugins/service-store/migrations.ts @@ -284,4 +284,86 @@ export const MIGRATIONS: readonly MigrationEntry[] = [ addColumnIfNotExists(db, "displays", "kiosk_id", "INTEGER REFERENCES kiosks(id) ON DELETE SET NULL"); }, `CREATE INDEX IF NOT EXISTS idx_displays_kiosk ON displays(kiosk_id)`, + + // ---- v0.3: decouple layouts from displays via join table ------------------- + // Layouts become standalone entities; displays maintain a list of available + // layouts via display_layouts. Old layouts.display_id column is kept (SQLite + // can't drop columns) but no longer used by the application. + `CREATE TABLE IF NOT EXISTS display_layouts ( + display_id INTEGER NOT NULL REFERENCES displays(id) ON DELETE CASCADE, + layout_id INTEGER NOT NULL REFERENCES layouts(id) ON DELETE CASCADE, + PRIMARY KEY (display_id, layout_id) + ) STRICT`, + `CREATE INDEX IF NOT EXISTS idx_display_layouts_layout ON display_layouts(layout_id)`, + (db: DatabaseSync) => { + // Backfill: every existing layout that has display_id gets attached to + // that display via the new join table. Idempotent via INSERT OR IGNORE. + const cols = db.prepare(`PRAGMA table_info("layouts")`).all() as Array<{ name: string }>; + if (!cols.some((c) => c.name === "display_id")) return; + const rows = db + .prepare(`SELECT id, display_id FROM layouts WHERE display_id IS NOT NULL`) + .all() as Array<{ id: number; display_id: number | null }>; + const ins = db.prepare( + `INSERT OR IGNORE INTO display_layouts (display_id, layout_id) VALUES (?, ?)`, + ); + for (const r of rows) { + if (r.display_id != null) ins.run(r.display_id, r.id); + } + }, + + // ---- v0.4: cells own their position; drop regions/grid_*/is_default ---------- + // layout_cells now have row/col/row_span/col_span columns directly. Existing + // cells get backfilled by parsing layouts.regions JSON and matching on + // region_name. The old columns (regions, grid_cols, grid_rows, is_default, + // region_name) are kept on the row (SQLite can't drop columns) but no longer + // used by the application. + (db: DatabaseSync) => { + addColumnIfNotExists(db, "layout_cells", "row", "INTEGER NOT NULL DEFAULT 0"); + addColumnIfNotExists(db, "layout_cells", "col", "INTEGER NOT NULL DEFAULT 0"); + addColumnIfNotExists(db, "layout_cells", "row_span", "INTEGER NOT NULL DEFAULT 1"); + addColumnIfNotExists(db, "layout_cells", "col_span", "INTEGER NOT NULL DEFAULT 1"); + + // Backfill: parse each layout's regions JSON, match cells by region_name, + // copy row/col/rowSpan/colSpan onto the cell row. Only update cells that + // still have the default 0,0,1,1 (idempotent re-runs become no-ops once the + // operator has edited cells through the new UI). + const cellCols = db.prepare(`PRAGMA table_info("layout_cells")`).all() as Array<{ name: string }>; + const hasRegionName = cellCols.some((c) => c.name === "region_name"); + const layoutCols = db.prepare(`PRAGMA table_info("layouts")`).all() as Array<{ name: string }>; + const hasRegions = layoutCols.some((c) => c.name === "regions"); + if (!hasRegionName || !hasRegions) return; + + const layouts = db + .prepare(`SELECT id, regions FROM layouts WHERE regions IS NOT NULL AND regions != '[]'`) + .all() as Array<{ id: number; regions: string }>; + const updateCell = db.prepare( + `UPDATE layout_cells + SET row = ?, col = ?, row_span = ?, col_span = ? + WHERE id = ? + AND row = 0 AND col = 0 AND row_span = 1 AND col_span = 1`, + ); + for (const l of layouts) { + let regions: Array<{ name: string; row: number; col: number; rowSpan: number; colSpan: number }>; + try { + regions = JSON.parse(l.regions); + } catch { + continue; + } + if (!Array.isArray(regions)) continue; + const cells = db + .prepare(`SELECT id, region_name FROM layout_cells WHERE layout_id = ?`) + .all(l.id) as Array<{ id: number; region_name: string }>; + for (const c of cells) { + const r = regions.find((reg) => reg.name === c.region_name); + if (!r) continue; + updateCell.run( + Number(r.row) || 0, + Number(r.col) || 0, + Number(r.rowSpan) || 1, + Number(r.colSpan) || 1, + c.id, + ); + } + } + }, ]; diff --git a/server/src/plugins/service-store/repository.ts b/server/src/plugins/service-store/repository.ts index 388c1a1..9878e6f 100644 --- a/server/src/plugins/service-store/repository.ts +++ b/server/src/plugins/service-store/repository.ts @@ -484,42 +484,87 @@ export class Repository { return r ? rowToLayout(r as Record) : null; } + /** + * @deprecated Use `listLayoutsForDisplay` which goes through the + * `display_layouts` join table. Kept as a thin alias for any + * callers still on the old API. + */ layoutsForDisplay(displayId: number): Layout[] { + return this.listLayoutsForDisplay(displayId); + } + + /** All layouts attached to the given display, via display_layouts. */ + listLayoutsForDisplay(displayId: number): Layout[] { const rs = this.prep( - "SELECT * FROM layouts WHERE display_id = ? ORDER BY name", + `SELECT l.* FROM layouts l + JOIN display_layouts dl ON dl.layout_id = l.id + WHERE dl.display_id = ? + ORDER BY l.name`, ).all(displayId); return rs.map((r) => rowToLayout(r as Record)); } + /** Inverse: all displays that have this layout attached. */ + listDisplaysForLayout(layoutId: number): Display[] { + const rs = this.prep( + `SELECT d.* FROM displays d + JOIN display_layouts dl ON dl.display_id = d.id + WHERE dl.layout_id = ? + ORDER BY d."index"`, + ).all(layoutId); + return rs.map((r) => rowToDisplay(r as Record)); + } + + /** Idempotent attach. */ + attachLayoutToDisplay(displayId: number, layoutId: number): void { + this.prep( + `INSERT OR IGNORE INTO display_layouts (display_id, layout_id) + VALUES (?, ?)`, + ).run(displayId, layoutId); + void this.notify("display_layouts", "create", layoutId); + } + + /** Detach. If the display's default_layout_id pointed at this layout, clear it. */ + detachLayoutFromDisplay(displayId: number, layoutId: number): void { + this.db + .prepare(`DELETE FROM display_layouts WHERE display_id = ? AND layout_id = ?`) + .run(displayId, layoutId); + this.db + .prepare( + `UPDATE displays SET default_layout_id = NULL + WHERE id = ? AND default_layout_id = ?`, + ) + .run(displayId, layoutId); + void this.notify("display_layouts", "delete", layoutId); + } + createLayout(input: { name: string; description?: string | null; - template_id?: number | null; - regions: unknown; - grid_cols: number; - grid_rows: number; - display_id: number; priority?: string; cooling_timeout_seconds?: number | null; preload_camera_ids?: number[]; - is_default?: boolean; resets_idle_timer?: boolean; }): Layout { + // Legacy NOT NULL columns (template_id, display_id, regions, grid_*) are + // populated with sentinel values: cells own their position now and the + // grid is computed at read time. The columns will be dropped by a future + // migration — until then they're inert. const result = this.prep( `INSERT INTO layouts (name, description, template_id, regions, grid_cols, grid_rows, display_id, priority, cooling_timeout_seconds, preload_camera_ids, is_default, resets_idle_timer) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ).run( input.name, input.description ?? null, - input.template_id ?? null, - J(input.regions), - input.grid_cols, - input.grid_rows, - input.display_id, + null, + J([]), + 1, + 1, + 0, input.priority ?? "normal", input.cooling_timeout_seconds ?? null, J(input.preload_camera_ids ?? []), - B(input.is_default ?? false), + B(false), B(input.resets_idle_timer ?? true), ); const id = Number(result.lastInsertRowid); @@ -533,7 +578,7 @@ export class Repository { const sets: string[] = []; const vals: unknown[] = []; for (const [k, v] of Object.entries(patch)) { - if (k === "id") continue; + if (k === "id" || k === "display_id") continue; // display_id deprecated sets.push(`${k} = ?`); if (k === "preload_camera_ids" || k === "regions") vals.push(J(v)); else if (typeof v === "boolean") vals.push(B(v)); @@ -548,6 +593,9 @@ export class Repository { deleteLayout(id: number): void { this.db.prepare(`DELETE FROM layout_cells WHERE layout_id = ?`).run(id); this.db.prepare(`DELETE FROM layout_labels WHERE layout_id = ?`).run(id); + this.db.prepare(`DELETE FROM display_layouts WHERE layout_id = ?`).run(id); + // Any display whose default pointed here gets cleared. + this.db.prepare(`UPDATE displays SET default_layout_id = NULL WHERE default_layout_id = ?`).run(id); this.db.prepare(`DELETE FROM layouts WHERE id = ?`).run(id); void this.notify("layouts", "delete", id); } @@ -558,7 +606,10 @@ export class Repository { createLayoutCell(input: { layout_id: number; - region_name: string; + row: number; + col: number; + row_span?: number; + col_span?: number; content_type: string; camera_id?: number | null; stream_selector?: string | null; @@ -567,12 +618,19 @@ export class Repository { cooling_timeout_seconds?: number | null; options?: Record; }): LayoutCell { + // region_name column is legacy NOT NULL — synthesize a unique placeholder + // until the column is dropped by a future migration. Nothing reads it. + const placeholder = `cell_${input.layout_id}_${input.row}_${input.col}_${Date.now()}`; const result = this.prep( - `INSERT INTO layout_cells (layout_id, region_name, content_type, camera_id, stream_selector, web_url, html_content, cooling_timeout_seconds, options) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + `INSERT INTO layout_cells (layout_id, region_name, "row", col, row_span, col_span, content_type, camera_id, stream_selector, web_url, html_content, cooling_timeout_seconds, options) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ).run( input.layout_id, - input.region_name, + placeholder, + input.row, + input.col, + input.row_span ?? 1, + input.col_span ?? 1, input.content_type, input.camera_id ?? null, input.stream_selector ?? "auto", @@ -593,7 +651,8 @@ export class Repository { const vals: unknown[] = []; for (const [k, v] of Object.entries(patch)) { if (k === "id" || k === "layout_id") continue; - sets.push(`${k} = ?`); + const col = k === "row" ? `"row"` : k; + sets.push(`${col} = ?`); if (k === "options") vals.push(J(v)); else vals.push(v === undefined ? null : v); } @@ -608,15 +667,45 @@ export class Repository { void this.notify("layout_cells", "delete", id); } + /** + * Shift cells along an axis to make room for an insertion (or close a gap + * after a deletion). For axis="row", any cell whose `row >= fromIndex` has + * its row bumped by `delta`. Same for axis="col". Used by the visual + * builder when adding a cell to the top/left of an existing one. + */ + shiftCellsForLayout( + layoutId: number, + axis: "row" | "col", + fromIndex: number, + delta: number, + ): void { + if (delta === 0) return; + const colName = axis === "row" ? `"row"` : "col"; + this.db + .prepare( + `UPDATE layout_cells + SET ${colName} = ${colName} + ? + WHERE layout_id = ? + AND ${colName} >= ?`, + ) + .run(delta, layoutId, fromIndex); + void this.notify("layout_cells", "update", layoutId); + } + + listLayoutCells(layoutId: number): LayoutCell[] { + const rs = this.prep( + `SELECT * FROM layout_cells WHERE layout_id = ? ORDER BY "row", col`, + ).all(layoutId); + return rs.map((r) => rowToLayoutCell(r as Record)); + } + // =========================================================================== // display-chain bundle queries (kiosk → display → layouts → cells → cameras) // =========================================================================== + /** Bundle generation: layouts attached to a display via display_layouts. */ layoutsForDisplayId(displayId: number): Layout[] { - const rs = this.prep( - "SELECT * FROM layouts WHERE display_id = ? ORDER BY is_default DESC, name", - ).all(displayId); - return rs.map((r) => rowToLayout(r as Record)); + return this.listLayoutsForDisplay(displayId); } camerasForLayoutIds(layoutIds: number[]): Camera[] { @@ -1064,10 +1153,7 @@ export class Repository { } layoutCells(layoutId: number): LayoutCell[] { - const rs = this.prep( - "SELECT * FROM layout_cells WHERE layout_id = ?", - ).all(layoutId); - return rs.map((r) => rowToLayoutCell(r as Record)); + return this.listLayoutCells(layoutId); } layoutTemplates(ids: number[]): LayoutTemplate[] { diff --git a/server/src/schemas/wire/bundle.ts b/server/src/schemas/wire/bundle.ts index c4d5ade..ca3535f 100644 --- a/server/src/schemas/wire/bundle.ts +++ b/server/src/schemas/wire/bundle.ts @@ -58,27 +58,18 @@ const camera = av.object( { unknownKeys: "reject" }, ); -const layoutRegion = av.object( - { - name: av.string().minLength(1).maxLength(64), - row: av.int().min(0).max(11), - col: av.int().min(0).max(11), - rowSpan: av.int().min(1).max(12), - colSpan: av.int().min(1).max(12), - }, - { unknownKeys: "reject" }, -); - const layoutCell = av.object( { - region_name: av.string().minLength(1).maxLength(64), + row: av.int().min(0).max(63), + col: av.int().min(0).max(63), + row_span: av.int().min(1).max(64), + col_span: av.int().min(1).max(64), content_type: cellContentType, camera_id: av.nullable(av.int().min(1)), stream_selector: streamSelector, web_url: av.nullable(av.string()), html_content: av.nullable(av.string()), cooling_timeout_seconds: av.nullable(av.int().min(0)), - options: av.record(av.unknown()), }, { unknownKeys: "reject" }, ); @@ -87,14 +78,13 @@ const layout = av.object( { id: av.int().min(1), name: av.string().minLength(1).maxLength(128), - regions: av.array(layoutRegion), grid_cols: av.int().min(1).max(64), grid_rows: av.int().min(1).max(64), priority: layoutPriority, cooling_timeout_seconds: av.nullable(av.int().min(0)), preload_camera_ids: av.array(av.int().min(1)), - is_default: av.bool(), resets_idle_timer: av.bool(), + is_default: av.bool(), cells: av.array(layoutCell), }, { unknownKeys: "reject" }, diff --git a/server/src/shared/bundle.ts b/server/src/shared/bundle.ts index 9f1894a..ea51687 100644 --- a/server/src/shared/bundle.ts +++ b/server/src/shared/bundle.ts @@ -31,7 +31,10 @@ export interface BundleCamera { } export interface BundleCell { - region_name: string; + row: number; + col: number; + row_span: number; + col_span: number; content_type: string; camera_id: number | null; stream_selector: string | null; @@ -43,14 +46,16 @@ export interface BundleCell { export interface BundleLayout { id: number; name: string; - regions: unknown; + /** Computed from cells: max(col + col_span). 1 if no cells. */ grid_cols: number; + /** Computed from cells: max(row + row_span). 1 if no cells. */ grid_rows: number; priority: string; cooling_timeout_seconds: number | null; preload_camera_ids: number[]; - is_default: boolean; resets_idle_timer: boolean; + /** True if the kiosk's display has this layout as its default_layout_id. */ + is_default: boolean; cells: BundleCell[]; } @@ -97,21 +102,32 @@ export function generateBundle( // Collect all cameras referenced by cells in these layouts const cameras = repo.camerasForLayoutIds(layoutIds); + const defaultLayoutId = display.default_layout_id; const bundleLayouts: BundleLayout[] = layouts.map((l) => { const cells = repo.layoutCells(l.id); + let gridCols = 1; + let gridRows = 1; + for (const c of cells) { + const right = c.col + c.col_span; + const bottom = c.row + c.row_span; + if (right > gridCols) gridCols = right; + if (bottom > gridRows) gridRows = bottom; + } return { id: l.id, name: l.name, - regions: l.regions, - grid_cols: l.grid_cols, - grid_rows: l.grid_rows, + grid_cols: gridCols, + grid_rows: gridRows, priority: l.priority, cooling_timeout_seconds: l.cooling_timeout_seconds, preload_camera_ids: l.preload_camera_ids, - is_default: l.is_default, resets_idle_timer: l.resets_idle_timer, + is_default: defaultLayoutId === l.id, cells: cells.map((c) => ({ - region_name: c.region_name, + row: c.row, + col: c.col, + row_span: c.row_span, + col_span: c.col_span, content_type: c.content_type, camera_id: c.camera_id, stream_selector: c.stream_selector, diff --git a/server/src/shared/types.ts b/server/src/shared/types.ts index 09b6b7f..c40b4f3 100644 --- a/server/src/shared/types.ts +++ b/server/src/shared/types.ts @@ -141,13 +141,19 @@ export interface Layout { name: string; description: string | null; template_id: number | null; // deprecated — kept nullable for backward compat + /** @deprecated Cells now own their own position. Computed from cells at read time. */ regions: LayoutRegion[]; + /** @deprecated Computed from cells: max(col + col_span). */ grid_cols: number; + /** @deprecated Computed from cells: max(row + row_span). */ grid_rows: number; - display_id: number; + /** @deprecated Layouts are now standalone; use display_layouts join table. + * Column kept on the row for backward compat — will be removed in a future migration. */ + display_id: number | null; priority: LayoutPriority; cooling_timeout_seconds: number | null; preload_camera_ids: number[]; + /** @deprecated Per-display defaults live on `display.default_layout_id`. */ is_default: boolean; resets_idle_timer: boolean; } @@ -155,7 +161,12 @@ export interface Layout { export interface LayoutCell { id: number; layout_id: number; + /** @deprecated Cells own their position via row/col/row_span/col_span now. */ region_name: string; + row: number; + col: number; + row_span: number; + col_span: number; content_type: CellContentType; camera_id: number | null; stream_selector: StreamSelector; diff --git a/server/src/web-templates/admin-pages.tsx b/server/src/web-templates/admin-pages.tsx index 51e38ae..8702449 100644 --- a/server/src/web-templates/admin-pages.tsx +++ b/server/src/web-templates/admin-pages.tsx @@ -10,7 +10,6 @@ import type { Label, Layout as LayoutType, LayoutCell, - LayoutRegion, PairingCode, EventLog, } from "../shared/types.js"; @@ -879,7 +878,8 @@ export function LabelsPage(props: LabelsPageProps) { interface LayoutsPageProps { user: string; layouts: LayoutType[]; - displays: Map; + /** layout_id → number of displays the layout is attached to */ + displayCounts: Map; } export function LayoutsPage(props: LayoutsPageProps) { @@ -890,30 +890,33 @@ export function LayoutsPage(props: LayoutsPageProps) { New Layout

- A layout defines a grid of regions and binds cameras or other content into them for a display. + Layouts are standalone — they define a grid of regions and bind cameras or + other content into them. Attach a layout to one or more displays from the + display's edit page.

- - + - {props.layouts.length === 0 ? ( - + ) : ( props.layouts.map((l) => { - const disp = props.displays.get(l.display_id); + const count = props.displayCounts.get(l.id) ?? 0; return ( - - + - ); }) @@ -940,7 +940,6 @@ export function LayoutsPage(props: LayoutsPageProps) { interface LayoutNewPageProps { user: string; - displays: Display[]; error?: string; values?: Record; } @@ -954,85 +953,21 @@ export function LayoutNewPage(props: LayoutNewPageProps) { activeNav="layouts" flash={props.error ? { type: "error", message: props.error } : undefined} > -
- {/* Quick presets */} -
-

Quick Create from Preset

-

- Pick a preset grid layout. You can also define a custom grid below. -

-
- {[ - { preset: "fullscreen", label: "Fullscreen", desc: "1x1 grid, single region" }, - { preset: "2x2", label: "2x2 Grid", desc: "4 equal regions" }, - { preset: "1plus3", label: "1+3", desc: "Large left, 3 stacked right" }, - { preset: "3x3", label: "3x3 Grid", desc: "9 equal regions" }, - ].map((p) => ( -
- - - - - - - - ))} -
-
- - {/* Full form */} +
+

+ Create an empty layout. You'll add cells visually on the next page, + then attach the layout to one or more displays. +

-

Custom Layout

-
- - - {props.displays.length === 0 && ( -
- No displays exist yet. Pair a kiosk first to create a display. -
- )} -
- -
-
- - -
-
- - -
-
- -
- - -
- Array of regions: name, row, col, rowSpan, colSpan. Grid is zero-indexed. -
+ +
@@ -1044,18 +979,6 @@ export function LayoutNewPage(props: LayoutNewPageProps) {
-
- - -
- -
- -
-
NameGridDisplayDisplays PriorityDefault
No layouts created yet
No layouts created yet
{l.name}{String(l.grid_cols)}x{String(l.grid_rows)} ({String(l.regions.length)} regions){disp ? disp.name : `#${String(l.display_id)}`} + {count === 0 + ? unattached + : {String(count)} display{count !== 1 ? "s" : ""}} + {l.priority === "hot" ? hot @@ -922,9 +925,6 @@ export function LayoutsPage(props: LayoutsPageProps) { : normal } - {l.is_default ? Yes : ""} -
- - - - - - - - - {l.regions.length === 0 ? ( - - ) : ( - l.regions.map((r) => ( - - - - - - )) - )} - -
RegionPositionSize
No regions defined
{r.name}row {String(r.row)}, col {String(r.col)}{String(r.rowSpan)}x{String(r.colSpan)}
-
- - - {/* Cell assignments table */} -
-

Cell Assignments

-
- - - - - - - - - - {l.regions.map((r) => { - const cell = cellByRegion.get(r.name); - return ( - - - - - - ); - })} - -
RegionContentActions
{r.name} - {cell ? ( - - {cell.content_type} - {" "} - {cell.content_type === "camera" && cell.camera_id - ? (cameraById.get(cell.camera_id)?.name ?? `#${String(cell.camera_id)}`) - : cell.content_type === "web" && cell.web_url - ? {cell.web_url} - : cell.content_type === "html" - ? (custom HTML) - : "" - } - - ) : ( - Empty - )} - - {cell && ( -
- -
- )} -
-
-
- - {/* Add cell form */} -
-

Assign Content to Region

-
-
- - -
- -
- - -
- -
-
- - -
-
- - -
-
- - - - - - -
-
- - -
@@ -1359,7 +1297,10 @@ export function LayoutEditPage(props: LayoutEditPageProps) { interface DisplayEditPageProps { user: string; display: Display; - layouts: LayoutType[]; + /** Layouts currently attached to this display. */ + attachedLayouts: LayoutType[]; + /** All other layouts that could be attached. */ + availableLayouts: LayoutType[]; kioskName?: string | null; error?: string; success?: string; @@ -1383,7 +1324,7 @@ export function DisplayEditPage(props: DisplayEditPageProps) {

Display Info

Index: {String(d.index)}
-
Resolution: {String(d.width_px)}x{String(d.height_px)}
+
Resolution: {String(d.width_px)}x{String(d.height_px)} (reported by kiosk)
{d.kiosk_id && ( )} @@ -1398,13 +1339,15 @@ export function DisplayEditPage(props: DisplayEditPageProps) { -
Layout shown on idle revert.
+
+ Layout shown on idle revert. Only layouts attached below are eligible. +
@@ -1419,46 +1362,68 @@ export function DisplayEditPage(props: DisplayEditPageProps) {
Send CEC standby after this many seconds of inactivity. 0 to disable.
-
- - -
- -
- - -
- Back - {props.layouts.length > 0 && ( -
-

Layouts on This Display

-
+ {/* Layout attachments */} +
+

Available Layouts

+

+ Pick which layouts this display can show. The kiosk receives only + attached layouts in its bundle. +

+ + {props.attachedLayouts.length === 0 ? ( +

No layouts attached yet.

+ ) : ( +
+ - {props.layouts.map((l) => ( + {props.attachedLayouts.map((l) => ( - + + ))}
Name Priority Default
{l.name} {l.priority}{l.is_default ? Yes : ""}{d.default_layout_id === l.id ? Yes : ""} +
+ +
+
-
- )} + )} + + {props.availableLayouts.length > 0 ? ( +
+ + +
+ ) : ( +

+ {props.attachedLayouts.length === 0 + ? No layouts exist yet. Create one. + : "All existing layouts are already attached."} +

+ )} +
);