Compare commits

..

4 Commits

Author SHA1 Message Date
8aa698e65e chore: Update cargo.lock
Some checks failed
build / checks-matrix (push) Has been cancelled
build / codecov (push) Has been cancelled
docs / docs (push) Has been cancelled
build / checks-build (push) Has been cancelled
2026-01-20 21:06:57 +05:30
542679e386 chore: Update flake.lock 2026-01-20 21:02:14 +05:30
599d91700e Revert "feat(api): return (Self, AuthenticationResult) from authenticate"
This reverts commit c8c371230f.
2026-01-16 21:50:06 +05:30
c8c371230f feat(api): return (Self, AuthenticationResult) from authenticate
Some checks failed
build / checks-matrix (push) Has been cancelled
build / codecov (push) Has been cancelled
docs / docs (push) Has been cancelled
build / checks-build (push) Has been cancelled
2026-01-16 11:33:55 +05:30
10 changed files with 502 additions and 608 deletions

199
AGENTS.md
View File

@@ -1,199 +0,0 @@
# Agent Guidelines for Jello
This document provides guidelines for AI coding agents working on the Jello codebase.
## Project Overview
Jello is a WIP video client for Jellyfin written in Rust, focusing on HDR video playback using:
- **iced** - Primary GUI toolkit
- **gstreamer** - Video + audio decoding library
- **wgpu** - Rendering video from GStreamer in iced
## Build, Test, and Lint Commands
### Building
```bash
# Build in release mode
cargo build --release
cargo build -r
# Build specific workspace member
cargo build -p api
cargo build -p gst
cargo build -p ui-iced
# Run the application
cargo run --release -- -vv
just jello # Uses justfile
```
### Testing
```bash
# Run all tests in workspace
cargo test --workspace
# Run tests for a specific package
cargo test -p gst
cargo test -p api
cargo test -p iced-video
# Run a single test by name
cargo test test_appsink
cargo test -p gst test_appsink
# Run a specific test in a specific file
cargo test -p gst --test <test_file_name> <test_function_name>
# Run tests with output
cargo test -- --nocapture
cargo test -- --show-output
```
### Linting and Formatting
```bash
# Check code without building
cargo check
cargo check --workspace
# Run clippy (linter)
cargo clippy
cargo clippy --workspace
cargo clippy --workspace -- -D warnings
# Format code
cargo fmt
cargo fmt --all
# Check formatting without modifying files
cargo fmt --all -- --check
```
### Other Tools
```bash
# Check for security vulnerabilities and license compliance
cargo deny check
# Generate Jellyfin type definitions
just typegen
```
## Code Style Guidelines
### Rust Edition
- Use **Rust 2024 edition** (as specified in Cargo.toml files)
### Imports
- Use `use` statements at the top of files
- Group imports: std library, external crates, then local modules
- Use `crate::` for absolute paths within the crate
- Common pattern: create a `priv_prelude` module for internal imports
- Use `pub use` to re-export commonly used items
- Use wildcard imports (`use crate::priv_prelude::*;`) within internal modules when a prelude exists
Example:
```rust
use std::sync::Arc;
use reqwest::{Method, header::InvalidHeaderValue};
use serde::{Deserialize, Serialize};
use crate::errors::*;
```
### Naming Conventions
- **Types/Structs/Enums**: PascalCase (e.g., `JellyfinClient`, `Error`, `AppSink`)
- **Functions/Methods**: snake_case (e.g., `request_builder`, `stream_url`)
- **Variables**: snake_case (e.g., `access_token`, `device_id`)
- **Constants**: SCREAMING_SNAKE_CASE (e.g., `NEXT_ID`, `GST`)
- **Modules**: snake_case (e.g., `priv_prelude`, `error_stack`)
### Error Handling
- Use **`error-stack`** for error handling with context propagation
- Use **`thiserror`** for defining error types
- Standard error type pattern:
```rust
pub use error_stack::{Report, ResultExt};
#[derive(Debug, thiserror::Error)]
#[error("An error occurred")]
pub struct Error;
pub type Result<T, E = error_stack::Report<Error>> = core::result::Result<T, E>;
```
- Attach context to errors using `.change_context(Error)` and `.attach("description")`
- Use `#[track_caller]` on functions that may panic or error for better error messages
- Error handling example:
```rust
self.inner
.set_state(gstreamer::State::Playing)
.change_context(Error)
.attach("Failed to set pipeline to Playing state")?;
```
### Types
- Prefer explicit types over type inference when it improves clarity
- Use `impl Trait` for function parameters when appropriate (e.g., `impl AsRef<str>`)
- Use `Option<T>` and `Result<T, E>` idiomatically
- Use `Arc<T>` for shared ownership
- Use newtype patterns for semantic clarity (e.g., `ApiKey` wrapping `secrecy::SecretBox<String>`)
### Formatting
- Use 4 spaces for indentation
- Line length: aim for 100 characters, but not strictly enforced
- Use trailing commas in multi-line collections
- Follow standard Rust formatting conventions (enforced by `cargo fmt`)
### Documentation
- Add doc comments (`///`) for public APIs
- Use inline comments (`//`) sparingly, prefer self-documenting code
- Include examples in doc comments when helpful
### Async/Await
- Use `tokio` as the async runtime
- Mark async functions with `async` keyword
- Use `.await` for async operations
- Common pattern: `tokio::fs` for file operations
### Module Structure
- Use `mod.rs` or inline modules as appropriate
- Keep related functionality together
- Use `pub(crate)` for internal APIs
- Re-export commonly used items at crate root
### Macros
- Custom macros used: `wrap_gst!`, `parent_child!`
- Use macros for reducing boilerplate, only in the `gst` crate
### Testing
- Place tests in the same file with `#[test]` or `#[cfg(test)]`
- Use descriptive test function names (e.g., `test_appsink`, `unique_generates_different_ids`)
- Initialize tracing in tests when needed for debugging
### Dependencies
- Prefer well-maintained crates from crates.io
- Use `workspace.dependencies` for shared dependencies across workspace members
- Pin versions when stability is important
### Workspace Structure
The project uses a Cargo workspace with multiple members:
- `.` - Main jello binary
- `api` - Jellyfin API client
- `gst` - GStreamer wrapper
- `ui-iced` - Iced UI implementation
- `ui-gpui` - GPUI UI implementation (optional)
- `store` - Secret/data/storage management
- `jello-types` - Shared type definitions
- `typegen` - Jellyfin type generator
- `crates/iced-video` - Custom iced video widget
- `examples/hdr-gstreamer-wgpu` - HDR example
### Project-Specific Patterns
- Use `LazyLock` for global initialization (e.g., GStreamer init)
- Use the builder pattern with method chaining (e.g., `request_builder()`)
- Use `tap` crate's `.pipe()` for functional transformations
- Prefer `BTreeMap`/`BTreeSet` over `HashMap`/`HashSet` when order matters
- Prefer a functional programming style instead of an imperative one.
- When building UIs keep the handler and view code in the same module (eg. settings view and settings handle in the same file)
## License
All code in this project is MIT licensed.

362
Cargo.lock generated
View File

@@ -212,7 +212,7 @@ dependencies = [
"bytes", "bytes",
"iref", "iref",
"jiff", "jiff",
"reqwest 0.12.28", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"tap", "tap",
@@ -635,28 +635,6 @@ dependencies = [
"arrayvec", "arrayvec",
] ]
[[package]]
name = "aws-lc-rs"
version = "1.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.76" version = "0.3.76"
@@ -769,6 +747,18 @@ dependencies = [
"core2", "core2",
] ]
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]] [[package]]
name = "blade-graphics" name = "blade-graphics"
version = "0.7.0" version = "0.7.0"
@@ -888,6 +878,28 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79769241dcd44edf79a732545e8b5cec84c247ac060f5252cd51885d093a8fc" checksum = "e79769241dcd44edf79a732545e8b5cec84c247ac060f5252cd51885d093a8fc"
[[package]]
name = "bson"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3f109694c4f45353972af96bf97d8a057f82e2d6e496457f4d135b9867a518c"
dependencies = [
"ahash",
"base64",
"bitvec",
"getrandom 0.3.4",
"hex",
"indexmap",
"js-sys",
"rand 0.9.2",
"serde",
"serde_bytes",
"simdutf8",
"thiserror 2.0.18",
"time",
"uuid",
]
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.12.1" version = "1.12.1"
@@ -1129,19 +1141,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "chrono"
version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "ciborium" name = "ciborium"
version = "0.2.2" version = "0.2.2"
@@ -1290,15 +1289,6 @@ dependencies = [
"x11rb", "x11rb",
] ]
[[package]]
name = "cmake"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "cocoa" name = "cocoa"
version = "0.25.0" version = "0.25.0"
@@ -1757,6 +1747,15 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204"
[[package]]
name = "deranged"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [
"powerfmt",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "0.99.20" version = "0.99.20"
@@ -2342,12 +2341,6 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]] [[package]]
name = "funty" name = "funty"
version = "2.0.0" version = "2.0.0"
@@ -3429,30 +3422,6 @@ dependencies = [
"windows-registry 0.6.1", "windows-registry 0.6.1",
] ]
[[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 0.62.2",
]
[[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]] [[package]]
name = "iced" name = "iced"
version = "0.14.0" version = "0.14.0"
@@ -3464,7 +3433,7 @@ dependencies = [
"iced_futures", "iced_futures",
"iced_renderer", "iced_renderer",
"iced_runtime 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)", "iced_runtime 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)",
"iced_widget 0.14.2 (git+https://github.com/uttarayan21/iced?branch=0.14)", "iced_widget",
"iced_winit 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)", "iced_winit 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)",
"image", "image",
"thiserror 2.0.18", "thiserror 2.0.18",
@@ -3489,22 +3458,6 @@ dependencies = [
"wgpu", "wgpu",
] ]
[[package]]
name = "iced_aw"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cc84cc77dcb1c384c60792de025fb4a72e23c3d8c65c4a34691684875fc5403"
dependencies = [
"cfg-if",
"chrono",
"iced_core",
"iced_fonts",
"iced_widget 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-format",
"num-traits",
"web-time",
]
[[package]] [[package]]
name = "iced_beacon" name = "iced_beacon"
version = "0.14.0" version = "0.14.0"
@@ -3568,33 +3521,10 @@ source = "git+https://github.com/uttarayan21/iced?branch=0.14#6fbe1ec83722c67cf7
dependencies = [ dependencies = [
"iced_debug 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)", "iced_debug 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)",
"iced_program 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)", "iced_program 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)",
"iced_widget 0.14.2 (git+https://github.com/uttarayan21/iced?branch=0.14)", "iced_widget",
"log", "log",
] ]
[[package]]
name = "iced_fonts"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "214cff7c8499e328774216690e58e315a1a5f8f6fdd1035aed6298e62ffc4c1d"
dependencies = [
"iced_core",
"iced_fonts_macros",
"iced_widget 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "iced_fonts_macros"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef5125e110cb19cd1910a28298661c98c5d9ab02eef43594968352940e8752e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"ttf-parser 0.25.1",
]
[[package]] [[package]]
name = "iced_futures" name = "iced_futures"
version = "0.14.0" version = "0.14.0"
@@ -3742,20 +3672,6 @@ dependencies = [
"wgpu", "wgpu",
] ]
[[package]]
name = "iced_widget"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1596afa0d3109c2618e8bc12bae6c11d3064df8f95c42dfce570397dbe957ab"
dependencies = [
"iced_renderer",
"log",
"num-traits",
"rustc-hash 2.1.1",
"thiserror 2.0.18",
"unicode-segmentation",
]
[[package]] [[package]]
name = "iced_widget" name = "iced_widget"
version = "0.14.2" version = "0.14.2"
@@ -4917,6 +4833,12 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]] [[package]]
name = "num-derive" name = "num-derive"
version = "0.4.2" version = "0.4.2"
@@ -4928,16 +4850,6 @@ dependencies = [
"syn 2.0.114", "syn 2.0.114",
] ]
[[package]]
name = "num-format"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
dependencies = [
"arrayvec",
"itoa",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.46" version = "0.1.46"
@@ -5821,6 +5733,12 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.21" version = "0.2.21"
@@ -5998,7 +5916,6 @@ version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
dependencies = [ dependencies = [
"aws-lc-rs",
"bytes", "bytes",
"getrandom 0.3.4", "getrandom 0.3.4",
"lru-slab", "lru-slab",
@@ -6043,6 +5960,12 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@@ -6219,6 +6142,16 @@ dependencies = [
"font-types", "font-types",
] ]
[[package]]
name = "redb"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae323eb086579a3769daa2c753bb96deb95993c534711e0dbe881b5192906a06"
dependencies = [
"libc",
"uuid",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.16" version = "0.2.16"
@@ -6361,44 +6294,6 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "reqwest"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"mime",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "resvg" name = "resvg"
version = "0.45.1" version = "0.45.1"
@@ -6536,7 +6431,6 @@ version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [ dependencies = [
"aws-lc-rs",
"once_cell", "once_cell",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
@@ -6576,40 +6470,12 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-platform-verifier"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
dependencies = [
"core-foundation 0.10.0",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework 3.5.1",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.9" version = "0.103.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
dependencies = [ dependencies = [
"aws-lc-rs",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
"untrusted", "untrusted",
@@ -6737,15 +6603,6 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "secrecy"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
dependencies = [
"zeroize",
]
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.11.1" version = "2.11.1"
@@ -6808,6 +6665,16 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde_bytes"
version = "0.11.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
dependencies = [
"serde",
"serde_core",
]
[[package]] [[package]]
name = "serde_core" name = "serde_core"
version = "1.0.228" version = "1.0.228"
@@ -6973,6 +6840,12 @@ dependencies = [
"quote", "quote",
] ]
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]] [[package]]
name = "simplecss" name = "simplecss"
version = "0.2.2" version = "0.2.2"
@@ -7247,9 +7120,10 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
name = "store" name = "store"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bson",
"futures", "futures",
"parking_lot", "parking_lot",
"secrecy", "redb",
"serde", "serde",
"tokio", "tokio",
"uuid", "uuid",
@@ -7663,6 +7537,37 @@ dependencies = [
"zune-jpeg 0.4.21", "zune-jpeg 0.4.21",
] ]
[[package]]
name = "time"
version = "0.3.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
[[package]]
name = "time-macros"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd"
dependencies = [
"num-conv",
"time-core",
]
[[package]] [[package]]
name = "tiny-skia" name = "tiny-skia"
version = "0.11.4" version = "0.11.4"
@@ -8102,10 +8007,9 @@ dependencies = [
"gpui_util", "gpui_util",
"iced", "iced",
"iced-video", "iced-video",
"iced_aw",
"iced_wgpu", "iced_wgpu",
"iced_winit 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "iced_winit 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.13.1", "reqwest",
"tap", "tap",
"toml 0.9.11+spec-1.1.0", "toml 0.9.11+spec-1.1.0",
"tracing", "tracing",
@@ -8683,15 +8587,6 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "webpki-root-certs"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "weezl" name = "weezl"
version = "0.1.12" version = "0.1.12"
@@ -9606,6 +9501,15 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]] [[package]]
name = "x11-dl" name = "x11-dl"
version = "2.21.0" version = "2.21.0"

11
flake.lock generated
View File

@@ -34,10 +34,10 @@
"crates-io-index": { "crates-io-index": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1769614137, "lastModified": 1763363725,
"narHash": "sha256-3Td8fiv6iFVxeS0hYq3xdd10ZvUkC9INMAiQx/mECas=", "narHash": "sha256-cxr5xIKZFP45yV1ZHFTB1sHo5YGiR3FA8D9vAfDizMo=",
"ref": "refs/heads/master", "ref": "refs/heads/master",
"rev": "c7e7d6394bc95555d6acd5c6783855f47d64c90d", "rev": "0382002e816a4cbd17d8d5b172f08b848aa22ff6",
"shallow": true, "shallow": true,
"type": "git", "type": "git",
"url": "https://github.com/rust-lang/crates.io-index" "url": "https://github.com/rust-lang/crates.io-index"
@@ -50,9 +50,7 @@
}, },
"crates-nix": { "crates-nix": {
"inputs": { "inputs": {
"crates-io-index": [ "crates-io-index": "crates-io-index"
"crates-io-index"
]
}, },
"locked": { "locked": {
"lastModified": 1763364255, "lastModified": 1763364255,
@@ -126,7 +124,6 @@
"inputs": { "inputs": {
"advisory-db": "advisory-db", "advisory-db": "advisory-db",
"crane": "crane", "crane": "crane",
"crates-io-index": "crates-io-index",
"crates-nix": "crates-nix", "crates-nix": "crates-nix",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nix-github-actions": "nix-github-actions", "nix-github-actions": "nix-github-actions",

View File

@@ -9,14 +9,7 @@
url = "github:nix-community/nix-github-actions"; url = "github:nix-community/nix-github-actions";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
crates-io-index = { crates-nix.url = "github:uttarayan21/crates.nix";
url = "git+https://github.com/rust-lang/crates.io-index?shallow=1";
flake = false;
};
crates-nix = {
url = "github:uttarayan21/crates.nix";
inputs.crates-io-index.follows = "crates-io-index";
};
rust-overlay = { rust-overlay = {
url = "github:oxalica/rust-overlay"; url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@@ -186,38 +179,28 @@
devShells = rec { devShells = rec {
rust-shell = rust-shell =
pkgs.mkShell.override { pkgs.mkShell.override {
stdenv = pkgs.clangStdenv; stdenv =
# if pkgs.stdenv.isLinux if pkgs.stdenv.isLinux
# then (pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv) then (pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv)
# else pkgs.clangStdenv; else pkgs.clangStdenv;
} } (commonArgs
(commonArgs
// { // {
# GST_PLUGIN_PATH = "/run/current-system/sw/lib/gstreamer-1.0/"; # GST_PLUGIN_PATH = "/run/current-system/sw/lib/gstreamer-1.0/";
GIO_EXTRA_MODULES = "${pkgs.glib-networking}/lib/gio/modules"; GIO_EXTRA_MODULES = "${pkgs.glib-networking}/lib/gio/modules";
packages = with pkgs; packages = with pkgs;
[ [
toolchainWithRustAnalyzer toolchainWithRustAnalyzer
bacon cargo-nextest
cargo-audit
cargo-deny cargo-deny
cargo-expand cargo-expand
cargo-hack bacon
cargo-make cargo-make
cargo-nextest cargo-hack
cargo-outdated cargo-outdated
lld lld
lldb lldb
cargo-audit
(crates.buildCrate "cargo-with" {doCheck = false;}) (crates.buildCrate "cargo-with" {doCheck = false;})
(crates.buildCrate "dioxus-cli" {
nativeBuildInputs = with pkgs; [pkg-config];
buildInputs = [openssl];
doCheck = false;
})
(crates.buildCrate "cargo-hot" {
nativeBuildInputs = with pkgs; [pkg-config];
buildInputs = [openssl];
})
] ]
++ (lib.optionals pkgs.stdenv.isDarwin [ ++ (lib.optionals pkgs.stdenv.isDarwin [
apple-sdk_26 apple-sdk_26
@@ -228,7 +211,7 @@
samply samply
cargo-flamegraph cargo-flamegraph
perf perf
# mold mold
]); ]);
}); });
default = rust-shell; default = rust-shell;

View File

@@ -4,9 +4,10 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
bson = { version = "3.1.0", features = ["serde"] }
futures = "0.3.31" futures = "0.3.31"
parking_lot = "0.12.5" parking_lot = "0.12.5"
secrecy = "0.10.3" redb = { version = "3.1.0", features = ["uuid"] }
serde = "1.0.228" serde = "1.0.228"
tokio = { version = "1.48.0", features = ["rt"] } tokio = { version = "1.48.0", features = ["rt"] }
uuid = { version = "1.18.1", features = ["v4"] } uuid = "1.18.1"

View File

@@ -1,10 +1,10 @@
use std::collections::BTreeMap; pub mod redb;
pub mod sqlite;
pub mod toml;
use uuid::Uuid; pub trait Store {
fn image(&self, id: &str) -> Option<Vec<u8>>;
fn save_image(&mut self, id: &str, data: &[u8]);
}
pub struct ApiKey { pub struct Settings {}
inner: secrecy::SecretBox<String>,
}
pub struct SecretStore {
api_keys: BTreeMap<Uuid, ApiKey>,
}

225
store/src/redb.rs Normal file
View File

@@ -0,0 +1,225 @@
use std::{
borrow::Borrow,
collections::VecDeque,
marker::PhantomData,
path::Path,
sync::{Arc, RwLock, atomic::AtomicBool},
};
use futures::task::AtomicWaker;
use redb::{Error, Key, ReadableDatabase, TableDefinition, Value};
use serde::{Serialize, de::DeserializeOwned};
const USERS: TableDefinition<uuid::Uuid, Vec<u8>> = TableDefinition::new("users");
const SERVERS: TableDefinition<uuid::Uuid, Vec<u8>> = TableDefinition::new("servers");
const SETTINGS: TableDefinition<uuid::Uuid, Vec<u8>> = TableDefinition::new("settings");
#[derive(Debug)]
pub struct TableInner<T> {
db: Arc<T>,
}
impl<T> Clone for TableInner<T> {
fn clone(&self) -> Self {
Self {
db: Arc::clone(&self.db),
}
}
}
impl<T> TableInner<T> {
fn new(db: Arc<T>) -> Self {
Self { db }
}
}
impl TableInner<DatabaseHandle> {
async fn get<'a, K: Key, V: Serialize + DeserializeOwned>(
&self,
table: TableDefinition<'static, K, Vec<u8>>,
key: impl Borrow<K::SelfType<'a>>,
) -> Result<Option<V>> {
let db: &redb::Database = &self.db.as_ref().database;
let db_reader = db.begin_read()?;
let table = db_reader.open_table(table)?;
table
.get(key)?
.map(|value| bson::deserialize_from_slice(&value.value()))
.transpose()
.map_err(|e| redb::Error::Io(std::io::Error::other(e)))
}
async fn insert<
'a,
'b,
K: Key + Send + Sync,
V: Serialize + DeserializeOwned + Send + Sync + 'a,
>(
&'b self,
table: TableDefinition<'static, K, Vec<u8>>,
key: impl Borrow<K::SelfType<'a>> + Send + 'b,
value: V,
) -> Result<Option<V>> {
let db: &redb::Database = &self.db.as_ref().database;
// self.db
// .writing
// .store(true, std::sync::atomic::Ordering::SeqCst);
// let out = tokio::task::spawn_blocking(move || -> Result<Option<V>>
let out = tokio::task::spawn_blocking(|| -> Result<Option<V>> {
let db_writer = db.begin_write()?;
let out = {
let mut table = db_writer.open_table(table)?;
let serialized_value = bson::serialize_to_vec(&value)
.map_err(|e| redb::Error::Io(std::io::Error::other(e)))?;
let previous = table.insert(key, &serialized_value)?;
let out = previous
.map(|value| bson::deserialize_from_slice(&value.value()))
.transpose()
.map_err(|e| redb::Error::Io(std::io::Error::other(e)));
out
};
db_writer.commit()?;
out
})
.await
.expect("Task panicked");
out
}
}
// impl<K: Key, V: Serialize + DeserializeOwned> Table<K, V> for TableInner {
// async fn get(&self, key: K) -> Result<Option<Value>> {}
// async fn insert(&self, key: K, value: V) -> Result<Option<Value>> {}
// async fn modify(&self, key: K, v: FnOnce(V) -> V) -> Result<bool> {}
// async fn remove(&self, key: K) -> Result<Option<Value>> {}
// }
#[derive(Debug)]
pub struct Users<T>(TableInner<T>);
impl<T> Clone for Users<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T> Users<T> {
const TABLE: TableDefinition<'static, uuid::Uuid, Vec<u8>> = USERS;
}
#[derive(Debug)]
pub struct Servers<T>(TableInner<T>);
impl<T> Clone for Servers<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T> Servers<T> {
const TABLE: TableDefinition<'static, uuid::Uuid, Vec<u8>> = SERVERS;
}
#[derive(Debug)]
pub struct Settings<T>(TableInner<T>);
impl<T> Clone for Settings<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T> Settings<T> {
const TABLE: TableDefinition<'static, uuid::Uuid, Vec<u8>> = SETTINGS;
}
#[derive(Debug, Clone)]
pub struct Database {
users: Users<DatabaseHandle>,
servers: Servers<DatabaseHandle>,
settings: Settings<DatabaseHandle>,
handle: Arc<DatabaseHandle>,
}
#[derive(Debug)]
pub struct DatabaseHandle {
database: redb::Database,
writing: AtomicBool,
wakers: RwLock<VecDeque<AtomicWaker>>,
}
#[derive(Debug)]
pub struct DatabaseWriterGuard<'a> {
handle: &'a DatabaseHandle,
dropper: Arc<AtomicBool>,
}
// impl Drop for DatabaseWriterGuard<'_> {
// fn drop(&mut self) {
// self.handle
// .writing
// .store(false, std::sync::atomic::Ordering::SeqCst);
// let is_panicking = std::thread::panicking();
// let Ok(writer) = self.handle.wakers.write() else {
// if is_panicking {
// return;
// } else {
// panic!("Wakers lock poisoned");
// }
// }
// if let Some(waker) = (self.handle.wakers.write()).pop() {
// waker.wake();
// };
// // let mut wakers = self.handle.wakers.write().expect();
// // if let Some(waker) = self.handle.wakers.write().expect("Wakers lock poisoned").pop_front() {
// // waker.wake();
// // }
// // while let Some(waker) = wakers.pop_front() {
// // waker.wake();
// // }
// }
// }
type Result<O, E = redb::Error> = core::result::Result<O, E>;
pub trait Table<K: Key> {
fn insert<V: Serialize + DeserializeOwned>(
&self,
key: K,
value: V,
) -> impl Future<Output = Result<Option<V>>> + Send;
fn modify<V: Serialize + DeserializeOwned, O: Serialize + DeserializeOwned>(
&self,
key: K,
v: impl FnOnce(V) -> O,
) -> impl Future<Output = Result<bool>> + Send;
fn remove<V: Serialize + DeserializeOwned>(
&self,
key: K,
) -> impl Future<Output = Result<Option<V>>> + Send;
fn get<V: Serialize + DeserializeOwned>(
&self,
key: K,
) -> impl Future<Output = Result<Option<V>>> + Send;
}
impl Database {
pub fn create(path: impl AsRef<Path>) -> Result<Self, Error> {
let writing = AtomicBool::new(false);
let wakers = RwLock::new(VecDeque::new());
let db = redb::Database::create(path)?;
let db = Arc::new(DatabaseHandle {
database: db,
writing,
wakers,
});
let table_inner = TableInner::new(Arc::clone(&db));
let users = Users(table_inner.clone());
let servers = Servers(table_inner.clone());
let settings = Settings(table_inner.clone());
Ok(Self {
servers,
users,
settings,
handle: db,
})
}
}

View File

@@ -21,10 +21,9 @@ iced = { workspace = true, features = [
iced-video = { workspace = true } iced-video = { workspace = true }
iced_aw = "0.13.0"
iced_wgpu = "0.14.0" iced_wgpu = "0.14.0"
iced_winit = "0.14.0" iced_winit = "0.14.0"
reqwest = "0.13" reqwest = "0.12.24"
tap = "1.0.1" tap = "1.0.1"
toml = "0.9.8" toml = "0.9.8"
tracing = "0.1.41" tracing = "0.1.41"

View File

@@ -4,7 +4,6 @@ mod video;
mod shared_string; mod shared_string;
use iced_video::{Ready, Video, VideoHandle}; use iced_video::{Ready, Video, VideoHandle};
use shared_string::SharedString; use shared_string::SharedString;
use tap::Pipe as _;
use std::sync::Arc; use std::sync::Arc;
@@ -26,8 +25,6 @@ pub struct ItemCache {
pub tree: BTreeMap<Option<uuid::Uuid>, BTreeSet<uuid::Uuid>>, pub tree: BTreeMap<Option<uuid::Uuid>, BTreeSet<uuid::Uuid>>,
} }
const BACKGROUND_COLOR: iced::Color = iced::Color::from_rgba8(30, 30, 30, 0.7);
impl ItemCache { impl ItemCache {
pub fn insert(&mut self, parent: impl Into<Option<uuid::Uuid>>, item: Item) { pub fn insert(&mut self, parent: impl Into<Option<uuid::Uuid>>, item: Item) {
let parent = parent.into(); let parent = parent.into();
@@ -158,6 +155,8 @@ impl State {
query: None, query: None,
screen: Screen::Home, screen: Screen::Home,
settings: settings::SettingsState::default(), settings: settings::SettingsState::default(),
// username_input: String::new(),
// password_input: String::new(),
is_authenticated: false, is_authenticated: false,
video: None, video: None,
} }
@@ -173,8 +172,17 @@ pub enum Message {
OpenItem(Option<uuid::Uuid>), OpenItem(Option<uuid::Uuid>),
LoadedItem(Option<uuid::Uuid>, Vec<Item>), LoadedItem(Option<uuid::Uuid>, Vec<Item>),
Error(String), Error(String),
SetToken(String),
Back, Back,
Home, Home,
// Login {
// username: String,
// password: String,
// config: api::JellyfinConfig,
// },
// LoginSuccess(String),
// LoadedClient(api::JellyfinClient, bool),
// Logout,
Video(video::VideoMessage), Video(video::VideoMessage),
} }
@@ -241,6 +249,15 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
state.messages.push(err); state.messages.push(err);
Task::none() Task::none()
} }
Message::SetToken(token) => {
tracing::info!("Authenticated with token: {}", token);
state
.jellyfin_client
.as_mut()
.map(|mut client| client.set_token(token));
state.is_authenticated = true;
Task::none()
}
Message::Back => { Message::Back => {
state.current = state.history.pop().unwrap_or(None); state.current = state.history.pop().unwrap_or(None);
Task::none() Task::none()
@@ -251,6 +268,7 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
} }
Message::SearchQueryChanged(query) => { Message::SearchQueryChanged(query) => {
state.query = Some(query); state.query = Some(query);
// Handle search query change
Task::none() Task::none()
} }
Message::Search => { Message::Search => {
@@ -275,29 +293,9 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
} }
fn view(state: &State) -> Element<'_, Message> { fn view(state: &State) -> Element<'_, Message> {
let content = home(state);
match state.screen { match state.screen {
Screen::Settings => { Screen::Settings => settings::settings(state),
let settings = settings::settings(state); Screen::Home | _ => home(state),
let settings = container(settings)
.width(Length::FillPortion(4))
.height(Length::FillPortion(4))
.style(container::rounded_box)
.pipe(mouse_area)
.on_press(Message::Refresh)
.pipe(|c| iced::widget::column![space::vertical(), c, space::vertical()])
.pipe(container)
.width(Length::Fill)
.width(Length::Fill)
.align_y(Alignment::Center)
.align_x(Alignment::Center)
.style(|_| container::background(BACKGROUND_COLOR))
.padding(50)
.pipe(mouse_area)
.on_press(Message::Settings(settings::SettingsMessage::Close));
stack![content, settings].into()
}
Screen::Home | _ => content,
} }
} }
@@ -312,34 +310,38 @@ fn body(state: &State) -> Element<'_, Message> {
if let Some(ref video) = state.video { if let Some(ref video) = state.video {
video::player(video) video::player(video)
} else { } else {
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card)) scrollable(
.fluid(400) container(
.spacing(50) Grid::with_children(state.cache.items_of(state.current).into_iter().map(card))
.pipe(container) .fluid(400)
.spacing(50),
)
.padding(50) .padding(50)
.align_x(Alignment::Center) .align_x(Alignment::Center)
// .align_y(Alignment::Center) // .align_y(Alignment::Center)
.height(Length::Fill) .height(Length::Fill)
.width(Length::Fill) .width(Length::Fill),
.pipe(scrollable) )
.height(Length::Fill) .height(Length::Fill)
.into() .into()
} }
} }
fn header(state: &State) -> Element<'_, Message> { fn header(state: &State) -> Element<'_, Message> {
row([ row([
text( container(
state Button::new(
.jellyfin_client Text::new(
.as_ref() state
.map(|c| c.config.server_url.as_str()) .jellyfin_client
.unwrap_or("No Server"), .as_ref()
.map(|c| c.config.server_url.as_str())
.unwrap_or("No Server"),
)
.align_x(Alignment::Start),
)
.on_press(Message::Home),
) )
.align_x(Alignment::Start)
.pipe(button)
.on_press(Message::Home)
.pipe(container)
.padding(10) .padding(10)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
@@ -348,17 +350,18 @@ fn header(state: &State) -> Element<'_, Message> {
.style(container::rounded_box) .style(container::rounded_box)
.into(), .into(),
search(state), search(state),
row([ container(
button("Refresh").on_press(Message::Refresh).into(), row([
button("Settings") button("Refresh").on_press(Message::Refresh).into(),
.on_press(Message::Settings(settings::SettingsMessage::Open)) button("Settings")
.into(), .on_press(Message::Settings(settings::SettingsMessage::Open))
button("TestVideo") .into(),
.on_press(Message::Video(video::VideoMessage::Test)) button("TestVideo")
.into(), .on_press(Message::Video(video::VideoMessage::Test))
]) .into(),
.spacing(10) ])
.pipe(container) .spacing(10),
)
.padding(10) .padding(10)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
@@ -374,18 +377,19 @@ fn header(state: &State) -> Element<'_, Message> {
} }
fn search(state: &State) -> Element<'_, Message> { fn search(state: &State) -> Element<'_, Message> {
TextInput::new("Search...", state.query.as_deref().unwrap_or_default()) container(
.padding(10) TextInput::new("Search...", state.query.as_deref().unwrap_or_default())
.size(16) .padding(10)
.width(Length::Fill) .size(16)
.on_input(Message::SearchQueryChanged) .width(Length::Fill)
.on_submit(Message::Search) .on_input(Message::SearchQueryChanged)
.pipe(container) .on_submit(Message::Search),
.padding(10) )
.width(Length::Fill) .padding(10)
.height(Length::Shrink) .width(Length::Fill)
.style(container::rounded_box) .height(Length::Shrink)
.into() .style(container::rounded_box)
.into()
} }
fn footer(state: &State) -> Element<'_, Message> { fn footer(state: &State) -> Element<'_, Message> {

View File

@@ -66,7 +66,7 @@ pub enum ServerMessage {
Clear, Clear,
} }
#[derive(Debug, Clone, Default, PartialEq, Eq)] #[derive(Debug, Clone, Default)]
pub enum SettingsScreen { pub enum SettingsScreen {
#[default] #[default]
Main, Main,
@@ -195,28 +195,9 @@ impl ServerForm {
} }
mod screens { mod screens {
use iced_aw::Tabs;
use super::*; use super::*;
pub fn settings(state: &State) -> Element<'_, Message> { pub fn settings(state: &State) -> Element<'_, Message> {
Tabs::new(|f| Message::Settings(SettingsMessage::Select(f))) row([settings_list(state), settings_screen(state)]).into()
.push(
SettingsScreen::Main,
iced_aw::TabLabel::Text("General".into()),
main(state),
)
.push(
SettingsScreen::Servers,
iced_aw::TabLabel::Text("Servers".into()),
server(state),
)
.push(
SettingsScreen::Users,
iced_aw::TabLabel::Text("Users".into()),
user(state),
)
.set_active_tab(&state.settings.screen)
.into()
} }
pub fn settings_screen(state: &State) -> Element<'_, Message> { pub fn settings_screen(state: &State) -> Element<'_, Message> {
@@ -226,65 +207,64 @@ mod screens {
SettingsScreen::Users => user(state), SettingsScreen::Users => user(state),
}) })
.width(Length::FillPortion(10)) .width(Length::FillPortion(10))
.height(Length::Fill)
.style(|theme| container::background(theme.extended_palette().background.base.color))
.pipe(container)
.padding(10)
.style(|theme| container::background(theme.extended_palette().secondary.base.color))
.width(Length::FillPortion(10))
.into() .into()
} }
pub fn settings_list(state: &State) -> Element<'_, Message> { pub fn settings_list(state: &State) -> Element<'_, Message> {
column( scrollable(
[ column(
button(center_text("General")).on_press(Message::Settings( [
SettingsMessage::Select(SettingsScreen::Main), button(center_text("Main")).on_press(Message::Settings(
)), SettingsMessage::Select(SettingsScreen::Main),
button(center_text("Servers")).on_press(Message::Settings( )),
SettingsMessage::Select(SettingsScreen::Servers), button(center_text("Servers")).on_press(Message::Settings(
)), SettingsMessage::Select(SettingsScreen::Servers),
button(center_text("Users")).on_press(Message::Settings(SettingsMessage::Select( )),
SettingsScreen::Users, button(center_text("Users")).on_press(Message::Settings(
))), SettingsMessage::Select(SettingsScreen::Users),
] )),
.map(|p| p.clip(true).width(Length::Fill).into()), ]
.map(|p| p.clip(true).width(Length::Fill).into()),
)
.width(Length::FillPortion(2))
.spacing(10)
.padding(10),
) )
.width(Length::FillPortion(2))
.spacing(10)
.padding(10)
.pipe(scrollable)
.into() .into()
} }
pub fn main(state: &State) -> Element<'_, Message> { pub fn main(state: &State) -> Element<'_, Message> {
Column::new() // placeholder for now
.push(text("Main Settings")) container(
.push(toggler(true).label("HDR")) Column::new()
.spacing(20) .push(text("Main Settings"))
.padding(20) .push(toggler(true).label("Foobar"))
.pipe(container) .spacing(20)
.into() .padding(20),
)
.into()
} }
pub fn server(state: &State) -> Element<'_, Message> { pub fn server(state: &State) -> Element<'_, Message> {
Column::new() container(
.push(text("Server Settings")) Column::new()
.push(state.settings.server_form.view()) .push(text("Server Settings"))
.spacing(20) .push(state.settings.server_form.view())
.padding(20) // .push(toggler(false).label("Enable Server"))
.pipe(container) .spacing(20)
.into() .padding(20),
)
.into()
} }
pub fn user(state: &State) -> Element<'_, Message> { pub fn user(state: &State) -> Element<'_, Message> {
Column::new() container(
.push(text("User Settings")) Column::new()
.push(state.settings.login_form.view()) .push(text("User Settings"))
.spacing(20) .push(state.settings.login_form.view())
.padding(20) // .push(userlist(&state))
.pipe(container) .spacing(20)
.into() .padding(20),
)
.into()
} }
} }