updated AGENTS.md and pps site works again.
This commit is contained in:
17
AGENTS.md
17
AGENTS.md
@@ -1,21 +1,26 @@
|
|||||||
# Mize
|
# Mize
|
||||||
|
|
||||||
Mize is a strongly typed "filesystem" for the age of connectivity, elevating the Unix file philosophy into modern distributed computing.
|
Mize is many things.
|
||||||
|
- a "strongly typed filesystem for the age of everyone having many devices"
|
||||||
|
- a stdlib replacement
|
||||||
|
- a framework to make malleable/extensible/configurable/debugable software fast
|
||||||
|
- a OS userspace (mainly linux but later also any other kernels)
|
||||||
|
- a library for COP = Configuration Oriented Programming
|
||||||
|
- a thing providing one single API through which any data can be interacted with
|
||||||
|
|
||||||
## Codebase Structure
|
## Codebase Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
packages/
|
packages/
|
||||||
├── mize/ # Core Mize framework (Rust)
|
├── src/ # Core Mize framework (Rust)
|
||||||
├── marts/ # Mize parts collection (Rust/TypeScript)
|
├── marts/ # Mize parts collection (Rust/TypeScript)
|
||||||
├── mme/ # Mize Module Environment (Rust)
|
└── macros/ # Mize macros
|
||||||
├── ppc/ # Platform-specific components
|
|
||||||
├── vic/ # Victor CLI tool
|
|
||||||
└── ac_mize_macros/# Mize macros
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Key Components
|
## Key Components
|
||||||
|
|
||||||
|
All code written for, with or as part as mize (except for the small core) is written as a Mize Part. see `docs/part-system.md` for how that works.
|
||||||
|
|
||||||
To understand the main components of the architecture you can refer to `docs/architecture.md`.
|
To understand the main components of the architecture you can refer to `docs/architecture.md`.
|
||||||
|
|
||||||
## Require clarification and plan approval before making code changes
|
## Require clarification and plan approval before making code changes
|
||||||
|
|||||||
80
Cargo.lock
generated
80
Cargo.lock
generated
@@ -1053,6 +1053,30 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmd_lib"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc93f60133215cad2ef47e47db5a386c78874a2630c2c2be68f0f543b3ce0c2c"
|
||||||
|
dependencies = [
|
||||||
|
"cmd_lib_macros",
|
||||||
|
"env_logger",
|
||||||
|
"faccess",
|
||||||
|
"log",
|
||||||
|
"os_pipe",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmd_lib_macros"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd39b5c2657c1a0064aa5d73636dac4a217edeeb6c5f8a5f52c123a758462ece"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cocoa"
|
name = "cocoa"
|
||||||
version = "0.26.1"
|
version = "0.26.1"
|
||||||
@@ -2407,6 +2431,29 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_filter"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.11.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"env_filter",
|
||||||
|
"jiff",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equator"
|
name = "equator"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -2534,6 +2581,17 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "faccess"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ae66425802d6a903e268ae1a08b8c38ba143520f227a205edf4e9c7e3e26d5"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fallible-iterator"
|
name = "fallible-iterator"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -4616,6 +4674,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"clap",
|
"clap",
|
||||||
|
"cmd_lib",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
"dioxus-ssr",
|
"dioxus-ssr",
|
||||||
"flume 0.12.0",
|
"flume 0.12.0",
|
||||||
@@ -5167,6 +5226,17 @@ dependencies = [
|
|||||||
"mize",
|
"mize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mize-spacetime"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"chrono-tz",
|
||||||
|
"log",
|
||||||
|
"spacetimedb",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mize_macros"
|
name = "mize_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -5715,6 +5785,16 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_pipe"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "p256"
|
name = "p256"
|
||||||
version = "0.13.2"
|
version = "0.13.2"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ members = [
|
|||||||
"macros",
|
"macros",
|
||||||
"apps/os",
|
"apps/os",
|
||||||
"apps/obsidian-plugin",
|
"apps/obsidian-plugin",
|
||||||
|
"marts/spacetime",
|
||||||
]
|
]
|
||||||
default-members = [
|
default-members = [
|
||||||
"apps/obsidian-plugin",
|
"apps/obsidian-plugin",
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ fn tried_main() -> MizeResult<()> {
|
|||||||
//"matrix": marts::part_matrix,
|
//"matrix": marts::part_matrix,
|
||||||
"ppc.server": marts::ppc::server::part_ppc_server,
|
"ppc.server": marts::ppc::server::part_ppc_server,
|
||||||
"c2vi": marts::part_c2vi,
|
"c2vi": marts::part_c2vi,
|
||||||
"sci.timetrack": marts::timetrack::part_timetrack,
|
"sci.timetrack": marts::sci::timetrack::part_timetrack,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
625
docs/part-system.md
Normal file
625
docs/part-system.md
Normal file
@@ -0,0 +1,625 @@
|
|||||||
|
# Mize Part System (LLM generated guide)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Mize is built around a **parts architecture** that allows you to compose functionality modularly. Each part is a self-contained unit that can:
|
||||||
|
|
||||||
|
- Store and manage its own state
|
||||||
|
- Expose methods to other parts
|
||||||
|
- Register itself with the Mize instance
|
||||||
|
- Define configuration options
|
||||||
|
|
||||||
|
Parts are the primary way to extend Mize's functionality. The system is designed so that parts can depend on each other, and users can enable/disable parts as needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
|
||||||
|
### The MizePart Trait
|
||||||
|
|
||||||
|
All parts implement the `MizePart` trait, which provides:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait MizePart: MizePartGenerated {
|
||||||
|
fn name(&self) -> &'static str; // Part identifier
|
||||||
|
fn get_mize(&mut self) -> &mut Mize; // Access to the Mize instance
|
||||||
|
fn init(&mut self, mize: &mut Mize) -> MizeResult<()>; // Initialization
|
||||||
|
fn run(&mut self, mize: &mut Mize) -> MizeResult<()>; // Execution
|
||||||
|
fn deps(&self) -> &'static [&'static str]; // Dependencies
|
||||||
|
fn opts(&self, mize: &mut Mize); // Config options
|
||||||
|
// ... async variants
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Part Registration
|
||||||
|
|
||||||
|
Parts register themselves with the Mize instance using:
|
||||||
|
|
||||||
|
- **`mize.register_part(Box::new(MyPart { ... }))`** - For parts with state and methods
|
||||||
|
- **`mize.add_name_only_part("name")`** - For simple parts that only need to be known by name (and have no state)
|
||||||
|
|
||||||
|
### Part Access
|
||||||
|
|
||||||
|
Parts can access other parts using:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Get a part by name (returns a guard that ensures the part is returned)
|
||||||
|
let mut part = mize.get_part("part_name")?;
|
||||||
|
|
||||||
|
// Get a typed part (for parts with structs)
|
||||||
|
let mut typed_part = mize.get_part_native::<OtherPart>("other_part")?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: Users must ensure that dependent parts are enabled before the parts that depend on them. There's no automatic dependency resolution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Implementation (Compiled-in Parts)
|
||||||
|
|
||||||
|
Currently, all parts are compiled into the `marts` crate and registered via the `compile_in_parts!` macro in the application's `main.rs`.
|
||||||
|
|
||||||
|
### Example: `apps/os/src/main.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
compile_in_parts! {
|
||||||
|
mize,
|
||||||
|
"cli": marts::cli,
|
||||||
|
"auth": marts::part_client_auth,
|
||||||
|
"spacetime": marts::spacetime,
|
||||||
|
"ppc.server": marts::ppc::server::part_ppc_server,
|
||||||
|
"c2vi": marts::part_c2vi,
|
||||||
|
"sci": marts::sci::part_sci,
|
||||||
|
"sci.timetrack": marts::sci::timetrack::part_timetrack,
|
||||||
|
"sci.inbox": marts::sci::inbox::part_sci_inbox,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Each entry maps a part name to a function that registers the part with the Mize instance.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. The macro reads the `enable` config option (a list of part names)
|
||||||
|
2. For each enabled part, it calls the corresponding registration function
|
||||||
|
3. Each registration function creates the part and calls `mize.register_part()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Creating a New Part
|
||||||
|
|
||||||
|
### Option 1: Simple Name-Only Part
|
||||||
|
|
||||||
|
For parts that don't need state or methods:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// In your part module
|
||||||
|
pub fn part_my_simple_part(mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
// Just register the name
|
||||||
|
mize.add_name_only_part("my_simple_part");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Part with State and Methods (Using `mize_part` Macro)
|
||||||
|
|
||||||
|
For parts that need to store state and expose methods:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
|
||||||
|
|
||||||
|
#[mize_part] // or #[mize_part("custom_name")] for custom name
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MyPart {
|
||||||
|
mize: Mize, // Required field
|
||||||
|
counter: u32, // Your custom state
|
||||||
|
api_key: String, // More state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement MizePartCreate for parts that need custom initialization
|
||||||
|
impl MizePartCreate for MyPart {
|
||||||
|
// Optional: override create_generated for custom initialization
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MizePart for MyPart {
|
||||||
|
// Optional: override name if not using the struct name
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"my_part" // Default would be "mypart" from struct name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: define config options this part needs
|
||||||
|
fn opts(&self, mize: &mut Mize) {
|
||||||
|
mize.new_opt("my_part.api_key");
|
||||||
|
mize.new_opt("my_part.counter_initial");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: initialization logic
|
||||||
|
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
self.counter = mize.get_config("my_part.counter_initial")?.value_u32()?;
|
||||||
|
self.api_key = mize.get_config("my_part.api_key")?.value_string()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: define dependencies on other parts
|
||||||
|
fn deps(&self) -> &'static [&'static str] {
|
||||||
|
&["auth"] // This part requires "auth" to be loaded first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The registration function
|
||||||
|
pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
mize.register_part(Box::new(MyPart {
|
||||||
|
mize: mize.clone(),
|
||||||
|
counter: 0,
|
||||||
|
api_key: String::new(),
|
||||||
|
}))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can add methods to your part struct
|
||||||
|
impl MyPart {
|
||||||
|
pub fn increment(&mut self) -> u32 {
|
||||||
|
self.counter += 1;
|
||||||
|
self.counter
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_api_key(&self) -> &str {
|
||||||
|
&self.api_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using the Part from Another Part
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn part_another_part(mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
// Get the MyPart instance
|
||||||
|
let mut my_part = mize.get_part_native::<MyPart>("my_part")?;
|
||||||
|
|
||||||
|
// Call its methods
|
||||||
|
let count = my_part.increment();
|
||||||
|
let api_key = my_part.get_api_key();
|
||||||
|
|
||||||
|
// ... your logic
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The `mize_part` Macro
|
||||||
|
|
||||||
|
The `#[mize_part]` procedural macro simplifies part creation by automatically implementing the required traits.
|
||||||
|
|
||||||
|
### What It Generates
|
||||||
|
|
||||||
|
For a struct like:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[mize_part]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MyPart {
|
||||||
|
mize: Mize,
|
||||||
|
// ... your fields
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The macro generates:
|
||||||
|
|
||||||
|
1. **`MizePartGenerated` implementation** - Provides default implementations for the generated trait methods
|
||||||
|
2. **`MizePartCreateGenerated` implementation** - Provides a default `create_generated` method
|
||||||
|
3. **`MizePartCreateThisSucks` implementation** - Compatibility trait
|
||||||
|
4. **Extern "C" function** - Named `make_mize_part_<name>` for dynamic loading
|
||||||
|
|
||||||
|
### Custom Part Name
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[mize_part("custom_name")]
|
||||||
|
pub struct MyPart {
|
||||||
|
mize: Mize,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This will use "custom_name" instead of the derived "mypart".
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- The struct **must** have a field named `mize: Mize`
|
||||||
|
- The struct should derive `Default` or implement it
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Defining Config Options
|
||||||
|
(about to be deprecad, does nothing)
|
||||||
|
|
||||||
|
Parts declare what config options they need in their `opts()` method:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl MizePart for MyPart {
|
||||||
|
fn opts(&self, mize: &mut Mize) {
|
||||||
|
mize.new_opt("my_part.api_url");
|
||||||
|
mize.new_opt("my_part.timeout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Config Options
|
||||||
|
|
||||||
|
Parts retrieve config using `mize.get_config()`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
let api_url = mize.get_config("my_part.api_url")?.value_string()?;
|
||||||
|
let timeout = mize.get_config("my_part.timeout")?.value_u64()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Configuration
|
||||||
|
|
||||||
|
Users configure parts via TOML files or environment variables. Example `config.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[my_part]
|
||||||
|
api_url = "https://api.example.com"
|
||||||
|
timeout = 30
|
||||||
|
|
||||||
|
[enable]
|
||||||
|
# List of parts to enable
|
||||||
|
my_part = true
|
||||||
|
auth = true
|
||||||
|
spacetime = true
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via command line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mize --opt my_part.api_url=https://api.example.com run
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part Dependencies
|
||||||
|
|
||||||
|
### Declaring Dependencies
|
||||||
|
(about to be deprecad, does nothing)
|
||||||
|
|
||||||
|
Parts declare their dependencies in the `deps()` method:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl MizePart for MyPart {
|
||||||
|
fn deps(&self) -> &'static [&'static str] {
|
||||||
|
&["auth", "spacetime"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: This is currently informational only. Users must manually ensure dependencies are enabled.
|
||||||
|
|
||||||
|
### Accessing Dependencies
|
||||||
|
|
||||||
|
Use `get_part_native()` to access other parts:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
// Get the auth part
|
||||||
|
let mut auth = mize.get_part_native::<ClientAuth>("client_auth")?;
|
||||||
|
|
||||||
|
// Use it
|
||||||
|
let token = auth.id_token()?;
|
||||||
|
|
||||||
|
// Get spacetime
|
||||||
|
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Future Vision: Separate Crates
|
||||||
|
|
||||||
|
The goal is to move each part to its own crate following the naming convention **`mize-part-<name>`**. when creating a part make a new Cargo project like this already.
|
||||||
|
|
||||||
|
### Structure of a Part Crate
|
||||||
|
|
||||||
|
```
|
||||||
|
mize-part-discord/
|
||||||
|
├── Cargo.toml
|
||||||
|
└── src/
|
||||||
|
└── lib.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: `mize-part-discord/Cargo.toml`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "mize-part-discord"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mize = { path = "../mize" }
|
||||||
|
serenity = "0.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: `mize-part-discord/src/lib.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
|
||||||
|
use serenity::Client;
|
||||||
|
|
||||||
|
#[mize_part]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DiscordPart {
|
||||||
|
mize: Mize,
|
||||||
|
client: Option<Client>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MizePartCreate for DiscordPart {}
|
||||||
|
|
||||||
|
impl MizePart for DiscordPart {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"discord"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opts(&self, mize: &mut Mize) {
|
||||||
|
mize.new_opt("discord.token");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
let token = mize.get_config("discord.token")?.value_string()?;
|
||||||
|
// Initialize serenity client...
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The registration function - THIS IS WHAT MATTERS
|
||||||
|
pub fn part_discord(mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
mize.register_part(Box::new(DiscordPart {
|
||||||
|
mize: mize.clone(),
|
||||||
|
client: None,
|
||||||
|
}))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Separate Part Crates
|
||||||
|
|
||||||
|
In your application's `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
mize = { path = "../mize" }
|
||||||
|
mize-part-discord = { path = "../mize-part-discord" }
|
||||||
|
mize-part-auth = { path = "../mize-part-auth" }
|
||||||
|
```
|
||||||
|
|
||||||
|
In your application's `main.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
compile_in_parts! {
|
||||||
|
mize,
|
||||||
|
"discord": mize_part_discord::part_discord,
|
||||||
|
"auth": mize_part_auth::part_auth,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Example: Creating a New Part
|
||||||
|
|
||||||
|
Let's create a complete example of a new part called `mize-part-greeting`.
|
||||||
|
|
||||||
|
### Step 1: Create the Crate
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo new --lib mize-part-greeting
|
||||||
|
cd mize-part-greeting
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Edit `Cargo.toml`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "mize-part-greeting"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mize = { path = "../mize" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Create the Part (`src/lib.rs`)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
|
||||||
|
|
||||||
|
/// A simple greeting part that stores a name and can generate greetings
|
||||||
|
#[mize_part]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct GreetingPart {
|
||||||
|
mize: Mize,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MizePartCreate for GreetingPart {}
|
||||||
|
|
||||||
|
impl MizePart for GreetingPart {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"greeting"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opts(&self, mize: &mut Mize) {
|
||||||
|
mize.new_opt("greeting.name");
|
||||||
|
mize.new_opt("greeting.formal");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
self.name = mize
|
||||||
|
.get_config("greeting.name")?
|
||||||
|
.value_string()
|
||||||
|
.unwrap_or_else(|_| "World".to_string());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GreetingPart {
|
||||||
|
/// Generate a greeting message
|
||||||
|
pub fn greet(&self) -> String {
|
||||||
|
let formal = self
|
||||||
|
.mize
|
||||||
|
.get_config("greeting.formal")
|
||||||
|
.map(|v| v.value_bool().unwrap_or(false))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if formal {
|
||||||
|
format!("Good day, {}!", self.name)
|
||||||
|
} else {
|
||||||
|
format!("Hello, {}!", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the name
|
||||||
|
pub fn set_name(&mut self, name: String) {
|
||||||
|
self.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current name
|
||||||
|
pub fn get_name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registration function - this is what the compile_in_parts! macro will call
|
||||||
|
pub fn part_greeting(mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
mize.register_part(Box::new(GreetingPart {
|
||||||
|
mize: mize.clone(),
|
||||||
|
name: String::new(),
|
||||||
|
}))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Add to Your Application
|
||||||
|
|
||||||
|
In your application's `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
mize = { path = "../mize" }
|
||||||
|
mize-part-greeting = { path = "../mize-part-greeting" }
|
||||||
|
```
|
||||||
|
|
||||||
|
In your application's `main.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
compile_in_parts! {
|
||||||
|
mize,
|
||||||
|
"greeting": mize_part_greeting::part_greeting,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Use the Part
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// In another part or in your CLI
|
||||||
|
let mut greeting = mize.get_part_native::<GreetingPart>("greeting")?;
|
||||||
|
println!("{}", greeting.greet());
|
||||||
|
|
||||||
|
// Change the name
|
||||||
|
greeting.set_name("Alice".to_string());
|
||||||
|
println!("{}", greeting.greet());
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Configure It
|
||||||
|
|
||||||
|
Create a `config.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[greeting]
|
||||||
|
name = "Bob"
|
||||||
|
formal = true
|
||||||
|
|
||||||
|
[enable]
|
||||||
|
greeting = true
|
||||||
|
```
|
||||||
|
|
||||||
|
Run with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mize --config config.toml run
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Naming**: Use `mize-part-<name>` for crate names and `part_<name>` for registration functions
|
||||||
|
2. **State**: Store part-specific state in the part struct
|
||||||
|
3. **Dependencies**: Declare dependencies in `deps()` and document them
|
||||||
|
4. **Config**: Declare all config options in `opts()`
|
||||||
|
5. **Error Handling**: Use `MizeResult` and `mize_err!` for errors
|
||||||
|
6. **Documentation**: Document your part's purpose, config options, and methods
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- Parts are modular units of functionality in Mize
|
||||||
|
- Each part implements the `MizePart` trait
|
||||||
|
- Use `#[mize_part]` macro for parts with state and methods
|
||||||
|
- Register parts with `mize.register_part()` or `mize.add_name_only_part()`
|
||||||
|
- Parts access config via `mize.get_config()`
|
||||||
|
- Parts access other parts via `mize.get_part_native::<T>()`
|
||||||
|
- Users enable parts via the `enable` config option
|
||||||
|
- Future: Each part will be its own crate named `mize-part-<name>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Common Patterns
|
||||||
|
|
||||||
|
**Register a part:**
|
||||||
|
```rust
|
||||||
|
mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get a part:**
|
||||||
|
```rust
|
||||||
|
let mut part = mize.get_part_native::<MyPart>("my_part")?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get config:**
|
||||||
|
```rust
|
||||||
|
let value = mize.get_config("my_part.setting")?.value_string()?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Declare config option:**
|
||||||
|
```rust
|
||||||
|
mize.new_opt("my_part.setting");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Define a part struct:**
|
||||||
|
```rust
|
||||||
|
#[mize_part]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MyPart {
|
||||||
|
mize: Mize,
|
||||||
|
// your fields...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Registration function:**
|
||||||
|
```rust
|
||||||
|
pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -382,7 +382,7 @@ in {
|
|||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
ExecStart = if config.mize.debugBinary == null then "${self.packages.${system}.mize}/bin/mize daemon" else "${config.mize.debugBinary} daemon";
|
ExecStart = if config.mize.debugBinary == null then "${self.packages.${system}.mize}/bin/mize daemon" else "${config.mize.debugBinary} daemon";
|
||||||
After = [ "graphical-session.target" ];
|
#After = [ "graphical-session.target" ];
|
||||||
PartOf = [ "graphical-session.target" ]; # this should mean DISPLAY and WAYLAND_DISPLAY are set in the service env...
|
PartOf = [ "graphical-session.target" ]; # this should mean DISPLAY and WAYLAND_DISPLAY are set in the service env...
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ enum Literal {
|
|||||||
Int(i128),
|
Int(i128),
|
||||||
Float(f64),
|
Float(f64),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
#[allow(unused)]
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ axum-oidc = { version = "=1.0.0-dev-1", optional = true }
|
|||||||
tower = { version = "0.5.3", optional = true }
|
tower = { version = "0.5.3", optional = true }
|
||||||
tower-sessions = { version = "0.15.0", optional = true }
|
tower-sessions = { version = "0.15.0", optional = true }
|
||||||
tower-http = { version = "0.7.1", optional = true, features = [ "cors", "trace" ] }
|
tower-http = { version = "0.7.1", optional = true, features = [ "cors", "trace" ] }
|
||||||
|
cmd_lib = "2.0.1"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -291,8 +291,7 @@ fn export_timetrack(ctx: &mut ProcedureContext, username: String, date: String)
|
|||||||
info!("end_of_day: {}", end_timestamp);
|
info!("end_of_day: {}", end_timestamp);
|
||||||
|
|
||||||
let mut export = String::new();
|
let mut export = String::new();
|
||||||
export.push_str(&format!("# export for {}\n\n", date));
|
//export.push_str(&format!("### actions\n"));
|
||||||
export.push_str(&format!("## actions\n"));
|
|
||||||
|
|
||||||
let profile = ctx.with_tx(|tx| tx.db.profiles().username().find(&username)).ok_or(format!("Profile with username {} not found", username))?;
|
let profile = ctx.with_tx(|tx| tx.db.profiles().username().find(&username)).ok_or(format!("Profile with username {} not found", username))?;
|
||||||
|
|
||||||
@@ -306,17 +305,19 @@ fn export_timetrack(ctx: &mut ProcedureContext, username: String, date: String)
|
|||||||
.to_chrono_date_time()
|
.to_chrono_date_time()
|
||||||
.map_err(|err| err.to_string())?
|
.map_err(|err| err.to_string())?
|
||||||
.with_timezone(&Europe::Vienna)
|
.with_timezone(&Europe::Vienna)
|
||||||
.format("%H:%M:%S");
|
.format("%H:%M");
|
||||||
let end = event.end
|
let end = event.end
|
||||||
.to_chrono_date_time()
|
.to_chrono_date_time()
|
||||||
.map_err(|err| err.to_string())?
|
.map_err(|err| err.to_string())?
|
||||||
.with_timezone(&Europe::Vienna)
|
.with_timezone(&Europe::Vienna)
|
||||||
.format("%H:%M:%S");
|
.format("%H:%M");
|
||||||
|
|
||||||
let line = format!("{} to {} ... {}\n", start, end, event.action.name);
|
let line = format!("{} to {} ... {}\n", start, end, event.action.name);
|
||||||
export.push_str(line.as_str());
|
export.push_str(line.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Ok(export); // don't do thoughts right now
|
||||||
|
|
||||||
export.push_str("\n");
|
export.push_str("\n");
|
||||||
export.push_str("## thoughts\n");
|
export.push_str("## thoughts\n");
|
||||||
let thoughts = ctx.with_tx(|tx| {
|
let thoughts = ctx.with_tx(|tx| {
|
||||||
@@ -355,8 +356,8 @@ pub fn migrate(ctx: &mut ProcedureContext) -> String {
|
|||||||
#[procedure]
|
#[procedure]
|
||||||
pub fn test(ctx: &mut ProcedureContext) -> String {
|
pub fn test(ctx: &mut ProcedureContext) -> String {
|
||||||
let mut s = String::from("hoooooooooooooooooooooo\n");
|
let mut s = String::from("hoooooooooooooooooooooo\n");
|
||||||
let a: String = ctx.with_tx(|tx| {
|
let a: String = ctx.with_tx(|_tx| {
|
||||||
let mut s = String::new();
|
let s = String::new();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ pub fn part_client_auth(mize: &mut Mize) -> MizeResult<()> {
|
|||||||
mize: mize.clone(),
|
mize: mize.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut secrets = match load_secrets(mize) {
|
let secrets = match load_secrets(mize) {
|
||||||
Ok(secrets) => Some(secrets),
|
Ok(secrets) => Some(secrets),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::warn!("err reading secrets: {:?}", err);
|
tracing::warn!("err reading secrets: {:?}", err);
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ use clap::{Arg, Command};
|
|||||||
use mize::item::ItemData;
|
use mize::item::ItemData;
|
||||||
use rusqlite::{params, Connection};
|
use rusqlite::{params, Connection};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
use clap::ArgMatches;
|
||||||
|
use cmd_lib::run_cmd as sh;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult};
|
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult};
|
||||||
@@ -18,13 +20,13 @@ use generated::spacetime::{
|
|||||||
Action, ActivitiesTableAccess, Activity, DbConnection, LogEventsTableAccess, TaskPart, TaskPartTableAccess, activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess, update_activity, update_upcoming
|
Action, ActivitiesTableAccess, Activity, DbConnection, LogEventsTableAccess, TaskPart, TaskPartTableAccess, activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess, update_activity, update_upcoming
|
||||||
};
|
};
|
||||||
use spacetimedb_sdk::{
|
use spacetimedb_sdk::{
|
||||||
credentials, DbContext, Error, Event, Identity, Status, SubscriptionHandle, Table,
|
credentials, DbContext, Error, Event, Identity, Status,
|
||||||
TableWithPrimaryKey,
|
TableWithPrimaryKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
static MIZE_SRC: &str = "/home/me/work/config/mize";
|
static MIZE_SRC: &str = "/home/me/work/config/mize";
|
||||||
static SRC: &str = "/home/me/work/config";
|
static SRC: &str = "/home/me/work/config";
|
||||||
static RSYNC_EXCLUDES: &str = "--exclude=node_modules --exclude=target --exclude=mize_config.toml --exclude=rust_dist --exclude=deno_dist --exclude=dist --exclude=result --exclude=pkg";
|
static RSYNC_EXCLUDES: &str = "--exclude=node_modules --exclude=target --exclude=mize_config.toml --exclude=rust_dist --exclude=deno_dist --exclude=dist --exclude=result --exclude=pkg --exclude /src --exclude obsidian-brat --exclude forked-zed";
|
||||||
|
|
||||||
#[mize_part]
|
#[mize_part]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -78,7 +80,7 @@ pub fn part_c2vi(mize: &mut Mize) -> MizeResult<()> {
|
|||||||
name: "SC2 Publish Daily Journal".to_string(),
|
name: "SC2 Publish Daily Journal".to_string(),
|
||||||
callback: Box::new(|mut mize| {
|
callback: Box::new(|mut mize| {
|
||||||
let matrix = mize.get_part_native::<crate::matrix::MatrixPart>("matrix")?;
|
let matrix = mize.get_part_native::<crate::matrix::MatrixPart>("matrix")?;
|
||||||
matrix.send_message(room_id);
|
//matrix.send_message(room_id);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}),
|
}),
|
||||||
@@ -92,9 +94,11 @@ fn c2vi_deploy_things(mize: &mut Mize) -> MizeResult<()> {
|
|||||||
let mut cli = mize.get_part_native::<CliPart>("cli")?;
|
let mut cli = mize.get_part_native::<CliPart>("cli")?;
|
||||||
cli.set_command("dev");
|
cli.set_command("dev");
|
||||||
|
|
||||||
|
let bin_arg = Arg::new("bin").short('b');
|
||||||
|
|
||||||
cli.subcommand(
|
cli.subcommand(
|
||||||
Command::new("deploy-lush").alias("dlu").arg(&bin_arg),
|
Command::new("deploy-lush").alias("dlu").arg(&bin_arg),
|
||||||
Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "lush")),
|
Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "te")),
|
||||||
)?;
|
)?;
|
||||||
cli.subcommand(
|
cli.subcommand(
|
||||||
Command::new("deploy-mac").alias("dmc").arg(&bin_arg),
|
Command::new("deploy-mac").alias("dmc").arg(&bin_arg),
|
||||||
@@ -124,10 +128,6 @@ fn c2vi_deploy_things(mize: &mut Mize) -> MizeResult<()> {
|
|||||||
Command::new("deploy-spacetime").alias("dsp").arg(&bin_arg),
|
Command::new("deploy-spacetime").alias("dsp").arg(&bin_arg),
|
||||||
Box::new(deploy_spacetime),
|
Box::new(deploy_spacetime),
|
||||||
)?;
|
)?;
|
||||||
cli.subcommand(
|
|
||||||
Command::new("generate").alias("gen").arg(&bin_arg),
|
|
||||||
Box::new(md_generate),
|
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,33 +168,61 @@ pub fn use_spacetime<T: Fn(QueryBuilder)>(query: T) {
|
|||||||
//pub fn use_by_pk(identity: Identity)
|
//pub fn use_by_pk(identity: Identity)
|
||||||
//pub fn use_one<T>
|
//pub fn use_one<T>
|
||||||
|
|
||||||
|
|
||||||
pub fn c2vi_dash_main() -> Element {
|
pub fn c2vi_dash_main() -> Element {
|
||||||
let mut current = use_signal_sync(|| "".to_string());
|
let mut current = use_signal_sync(|| "".to_string());
|
||||||
|
let mut next = use_signal_sync(|| "".to_string());
|
||||||
//TaskPart::
|
//TaskPart::
|
||||||
//use_one()
|
//use_one()
|
||||||
use_effect(move || {
|
use_effect(move || {
|
||||||
let mut mize = use_context::<Mize>();
|
let mut mize = use_context::<Mize>();
|
||||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap();
|
let spacetime = match mize.get_part_native::<SpacetimePart>("spacetime") {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(e) => {
|
||||||
|
e.log();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
let con = spacetime.con();
|
let con = spacetime.con();
|
||||||
con.subscription_builder()
|
con.subscription_builder()
|
||||||
.on_applied(move |ctx| {
|
.on_applied(move |ctx| {
|
||||||
let data = ctx.db.task_part().identity().find(&ctx.identity()).unwrap();
|
let data = match ctx.db.task_part().identity().find(&ctx.identity()) {
|
||||||
|
Some(val) => val,
|
||||||
|
None => {
|
||||||
|
debug!("can't find task part for user");
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
current.set(data.current.name.clone());
|
current.set(data.current.name.clone());
|
||||||
|
if let Some(first_upcoming) = data.upcoming.first() {
|
||||||
|
next.set(first_upcoming.name.clone());
|
||||||
|
} else {
|
||||||
|
next.set("".to_string());
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.add_query(|q| q.from.task_part().r#where(|t| t.identity.eq(con.identity())))
|
.add_query(|q| q.from.task_part().r#where(|t| t.identity.eq(con.identity())))
|
||||||
.subscribe();
|
.subscribe();
|
||||||
con.db
|
con.db
|
||||||
.task_part()
|
.task_part()
|
||||||
.on_update(move |_, old: &TaskPart, new: &TaskPart| {
|
.on_update(move |_, _old: &TaskPart, new: &TaskPart| {
|
||||||
current.set(new.current.name.clone());
|
current.set(new.current.name.clone());
|
||||||
|
if let Some(first_upcoming) = new.upcoming.first() {
|
||||||
|
next.set(first_upcoming.name.clone());
|
||||||
|
} else {
|
||||||
|
next.set("".to_string());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
style: "text-align: center; padding: 2rem; font-size: 50px;",
|
style: "text-align: center; padding: 2rem; font-size: 120px;",
|
||||||
p {
|
p {
|
||||||
"{current}"
|
"{current}"
|
||||||
}
|
}
|
||||||
|
p {
|
||||||
|
style: "font-size: 50px; color: grey; opacity: 0.7;",
|
||||||
|
"{next}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -774,8 +802,7 @@ pub fn remote_deploy(matches: &ArgMatches, _: Mize, hostname: &str) -> MizeResul
|
|||||||
let bin = match matches.get_one::<String>("bin") {
|
let bin = match matches.get_one::<String>("bin") {
|
||||||
Some(val) => val.to_owned(),
|
Some(val) => val.to_owned(),
|
||||||
None => {
|
None => {
|
||||||
do_mize_restart = true;
|
"mize".to_owned()
|
||||||
format!("{}-os", hostname)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -785,7 +812,7 @@ pub fn remote_deploy(matches: &ArgMatches, _: Mize, hostname: &str) -> MizeResul
|
|||||||
"files" => "/home/files/here/config",
|
"files" => "/home/files/here/config",
|
||||||
"nico" => "/root/work/config-c2vi",
|
"nico" => "/root/work/config-c2vi",
|
||||||
"ppc-hosting" => "/root/host/c2vi-config",
|
"ppc-hosting" => "/root/host/c2vi-config",
|
||||||
_ => "main-idk",
|
_ => "/home/me/here/config",
|
||||||
};
|
};
|
||||||
let mize_store_dir = match hostname {
|
let mize_store_dir = match hostname {
|
||||||
"lush" => "/home/me/host/mize",
|
"lush" => "/home/me/host/mize",
|
||||||
@@ -793,13 +820,15 @@ pub fn remote_deploy(matches: &ArgMatches, _: Mize, hostname: &str) -> MizeResul
|
|||||||
"files" => "/home/files/storage/files/mize",
|
"files" => "/home/files/storage/files/mize",
|
||||||
"nico" => "/root/work/app-data/mize",
|
"nico" => "/root/work/app-data/mize",
|
||||||
"ppc-hosting" => "/root/host/mize",
|
"ppc-hosting" => "/root/host/mize",
|
||||||
_ => "/tmp/idk",
|
_ => "/home/me/host/mize",
|
||||||
};
|
};
|
||||||
let rclone_flag = match hostname {
|
let rclone_flag = match hostname {
|
||||||
"nico" => "--chown=root:root",
|
"nico" => "--chown=root:root",
|
||||||
|
"ppc-hosting" => "--chown=root:root",
|
||||||
_ => "",
|
_ => "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sh!(echo cmd: rsync -r -u -v -a --delete --perms $rclone_flag --include=".git/" --include=".git/**" --exclude="target" --mkpath $RSYNC_EXCLUDES $SRC/ $hostname:$remote_src_dir)?;
|
||||||
sh!(rsync -r -u -v -a --delete --perms $rclone_flag --include=".git/" --include=".git/**" --exclude="target" --mkpath $RSYNC_EXCLUDES $SRC/ $hostname:$remote_src_dir)?;
|
sh!(rsync -r -u -v -a --delete --perms $rclone_flag --include=".git/" --include=".git/**" --exclude="target" --mkpath $RSYNC_EXCLUDES $SRC/ $hostname:$remote_src_dir)?;
|
||||||
|
|
||||||
let ssh_cmd = format!(
|
let ssh_cmd = format!(
|
||||||
@@ -812,7 +841,7 @@ pub fn remote_deploy(matches: &ArgMatches, _: Mize, hostname: &str) -> MizeResul
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
tmux send-keys -t mize-deploy "DO_MIZE_RESTART={do_mize_restart}" Enter
|
tmux send-keys -t mize-deploy "DO_MIZE_RESTART={do_mize_restart}" Enter
|
||||||
tmux send-keys -t mize-deploy "cargo build --manifest-path {remote_src_dir}/Cargo.toml --bin {bin}" Enter
|
tmux send-keys -t mize-deploy "cargo build --manifest-path {remote_src_dir}/mize/Cargo.toml --bin {bin}" Enter
|
||||||
tmux send-keys -t mize-deploy 'BUILD_SUCCESS=$?' Enter
|
tmux send-keys -t mize-deploy 'BUILD_SUCCESS=$?' Enter
|
||||||
tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" > {mize_store_dir}/daemon.env' Enter
|
tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" > {mize_store_dir}/daemon.env' Enter
|
||||||
tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && echo "MIZE_CONFIG_FILES={mize_store_dir}/config.toml" >> {mize_store_dir}/daemon.env' Enter
|
tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && echo "MIZE_CONFIG_FILES={mize_store_dir}/config.toml" >> {mize_store_dir}/daemon.env' Enter
|
||||||
@@ -888,7 +917,7 @@ pub fn main_deploy(_: &ArgMatches, _: Mize) -> MizeResult<()> {
|
|||||||
);
|
);
|
||||||
std::env::set_var("SRC", SRC);
|
std::env::set_var("SRC", SRC);
|
||||||
}
|
}
|
||||||
let cmd = str!(
|
let cmd =
|
||||||
r#"
|
r#"
|
||||||
if ! tmux has-session -t mize-deploy 2>/dev/null; then
|
if ! tmux has-session -t mize-deploy 2>/dev/null; then
|
||||||
systemd-run --user --scope --description="mize deploy tmux session" tmux new-session -d -s mize-deploy "nix develop git+file://$SRC/mize#buildShell"
|
systemd-run --user --scope --description="mize deploy tmux session" tmux new-session -d -s mize-deploy "nix develop git+file://$SRC/mize#buildShell"
|
||||||
@@ -898,8 +927,7 @@ pub fn main_deploy(_: &ArgMatches, _: Mize) -> MizeResult<()> {
|
|||||||
tmux send-keys -t mize-deploy "cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize" Enter
|
tmux send-keys -t mize-deploy "cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize" Enter
|
||||||
tmux send-keys -t mize-deploy 'echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" > /home/me/work/app-data/mize/daemon.env' Enter
|
tmux send-keys -t mize-deploy 'echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" > /home/me/work/app-data/mize/daemon.env' Enter
|
||||||
tmux send-keys -t mize-deploy "systemctl --user restart mize" Enter
|
tmux send-keys -t mize-deploy "systemctl --user restart mize" Enter
|
||||||
"#
|
"#;
|
||||||
);
|
|
||||||
sh!(/bin/sh -c "$cmd")?;
|
sh!(/bin/sh -c "$cmd")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ pub fn cli(mize: &mut Mize) -> MizeResult<()> {
|
|||||||
)?;
|
)?;
|
||||||
mize.add_listener(
|
mize.add_listener(
|
||||||
"cli.spawn_done",
|
"cli.spawn_done",
|
||||||
Box::new(move |mut msg, mize| {
|
Box::new(move |_msg, _mize| {
|
||||||
log::info!("before send");
|
log::info!("before send");
|
||||||
spawn_wait_tx.send(());
|
spawn_wait_tx.send(());
|
||||||
log::info!("after send");
|
log::info!("after send");
|
||||||
@@ -256,7 +256,7 @@ impl CliPart {
|
|||||||
action: T,
|
action: T,
|
||||||
) -> MizeResult<()> {
|
) -> MizeResult<()> {
|
||||||
let name = subcmd.get_name().to_string();
|
let name = subcmd.get_name().to_string();
|
||||||
let mut cmd = self
|
let cmd = self
|
||||||
.cmds
|
.cmds
|
||||||
.remove(&self.current_cmd_name)
|
.remove(&self.current_cmd_name)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -299,6 +299,7 @@ impl CliPart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// impl smth like this to run cli commands in the daemon.
|
// impl smth like this to run cli commands in the daemon.
|
||||||
|
/*
|
||||||
pub fn mize_thin_cli_client(bin_name: Option<&str>) -> MizeResult<()> {
|
pub fn mize_thin_cli_client(bin_name: Option<&str>) -> MizeResult<()> {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
@@ -353,3 +354,4 @@ pub fn mize_thin_cli_client(bin_name: Option<&str>) -> MizeResult<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
use mize::{Mize, MizeResult, MizeError, mize_err};
|
use mize::{Mize, MizeResult, MizeError, mize_err};
|
||||||
|
|
||||||
use crate::CliPart;
|
use crate::CliPart;
|
||||||
|
use clap::Arg;
|
||||||
|
|
||||||
// spacetime gen command: sh!(RUST_LOG=error RUST_BACKTRACE=0 spacetime generate --build-options="--lint-dir=" -p $SRC/mize/marts/spacetime -o $SRC/mize/marts/generated/src/spacetime --lang rust)?;
|
// spacetime gen command: sh!(RUST_LOG=error RUST_BACKTRACE=0 spacetime generate --build-options="--lint-dir=" -p $SRC/mize/marts/spacetime -o $SRC/mize/marts/generated/src/spacetime --lang rust)?;
|
||||||
pub fn part_dev(mize: &mut Mize) -> MizeResult<()> {
|
pub fn part_dev(mize: &mut Mize) -> MizeResult<()> {
|
||||||
let mut cli = mize.get_part_native::<CliPart>("cli")?;
|
let mut cli = mize.get_part_native::<CliPart>("cli")?;
|
||||||
cli.set_command("dev");
|
cli.set_command("dev");
|
||||||
|
|
||||||
let bin_arg = Arg::new("bin").short('b');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ pub use auth::*;
|
|||||||
|
|
||||||
pub mod ppc;
|
pub mod ppc;
|
||||||
|
|
||||||
pub mod timetrack;
|
|
||||||
|
|
||||||
#[cfg(feature = "target-obsidian")]
|
#[cfg(feature = "target-obsidian")]
|
||||||
pub mod matrix;
|
pub mod matrix;
|
||||||
|
|||||||
@@ -1,194 +0,0 @@
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use axum::response::Html;
|
|
||||||
|
|
||||||
pub fn main_page() -> Html<String> {
|
|
||||||
let html_output = dioxus_ssr::render_element(rsx! {
|
|
||||||
div {
|
|
||||||
h1 { "Hello from Dioxus SSR!" }
|
|
||||||
p { "This was rendered on the server and sent via Axum." }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Html(html_output)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main_page_element() -> Element {
|
|
||||||
rsx! {
|
|
||||||
"hello world"
|
|
||||||
div {
|
|
||||||
class: "min-h-screen bg-gray-100 items-center flex h-screen items-center justify-center",
|
|
||||||
|
|
||||||
// Header
|
|
||||||
header {
|
|
||||||
class: "bg-blue-600 text-white py-4",
|
|
||||||
div {
|
|
||||||
class: "container mx-auto px-4",
|
|
||||||
h1 {
|
|
||||||
class: "text-2xl font-bold",
|
|
||||||
"People Corner (PPC)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main content
|
|
||||||
main {
|
|
||||||
class: "container mx-auto px-4 py-8",
|
|
||||||
|
|
||||||
// Hero section
|
|
||||||
section {
|
|
||||||
class: "bg-white rounded-lg shadow-md p-6 mb-8",
|
|
||||||
h2 {
|
|
||||||
class: "text-xl font-semibold mb-4 text-blue-600",
|
|
||||||
"Welcome to People Corner"
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
class: "text-gray-700 mb-4",
|
|
||||||
"PPC is a social platform for intentional people who want to get more out of life together."
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
class: "text-gray-700",
|
|
||||||
"We're building a community-driven social media network that brings people together to do cool things, share resources, and support each other."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mission section
|
|
||||||
section {
|
|
||||||
class: "bg-white rounded-lg shadow-md p-6 mb-8",
|
|
||||||
h2 {
|
|
||||||
class: "text-xl font-semibold mb-4 text-blue-600",
|
|
||||||
"Our Mission"
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
class: "list-disc pl-6 space-y-2",
|
|
||||||
li { "Bring people together to get more out of life" }
|
|
||||||
li { "Create meaningful connections beyond superficial social media" }
|
|
||||||
li { "Support intentional living and personal growth" }
|
|
||||||
li { "Build a platform by people, for people" }
|
|
||||||
li { "Foster real-world meetups and collaborations" }
|
|
||||||
li { "Create a social media for diligent, intentional people" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key concepts section
|
|
||||||
section {
|
|
||||||
class: "bg-white rounded-lg shadow-md p-6",
|
|
||||||
h2 {
|
|
||||||
class: "text-xl font-semibold mb-4 text-blue-600",
|
|
||||||
"Key Concepts"
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
class: "list-disc pl-6 space-y-2",
|
|
||||||
li {
|
|
||||||
strong { "Trust Network: " }
|
|
||||||
"A system for building and maintaining trust between members"
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
strong { "Intentional Communities: " }
|
|
||||||
"Both online and physical spaces for like-minded people"
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
strong { "Resource Sharing: " }
|
|
||||||
"Platform for sharing skills, knowledge, and physical resources"
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
strong { "Real Connections: " }
|
|
||||||
"Focus on meaningful relationships over superficial interactions"
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
strong { "Personal Growth: " }
|
|
||||||
"Support for individuals to achieve their life goals"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Footer
|
|
||||||
footer {
|
|
||||||
class: "bg-blue-600 text-white py-4 mt-8",
|
|
||||||
div {
|
|
||||||
class: "container mx-auto px-4 text-center",
|
|
||||||
p { "© 2024 People Corner. All rights reserved." }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// About page component
|
|
||||||
pub fn about_page() -> Element {
|
|
||||||
rsx! {
|
|
||||||
div {
|
|
||||||
class: "container mx-auto px-4 py-8",
|
|
||||||
h1 {
|
|
||||||
class: "text-2xl font-bold mb-4 text-blue-600",
|
|
||||||
"About PPC"
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
class: "text-gray-700 mb-4",
|
|
||||||
"People Corner (PPC) is more than just a social platform - it's a movement to bring intentional people together to create meaningful connections and achieve more in life."
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
class: "text-gray-700",
|
|
||||||
"Our vision is to build a world where people support each other, share resources, and grow together through real-world interactions and digital connections."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contact page component
|
|
||||||
pub fn contact_page() -> Element {
|
|
||||||
rsx! {
|
|
||||||
div {
|
|
||||||
class: "container mx-auto px-4 py-8",
|
|
||||||
h1 {
|
|
||||||
class: "text-2xl font-bold mb-4 text-blue-600",
|
|
||||||
"Contact Us"
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
class: "text-gray-700 mb-4",
|
|
||||||
"We'd love to hear from you! Whether you have questions, feedback, or want to get involved, please reach out."
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
class: "max-w-md space-y-4",
|
|
||||||
div {
|
|
||||||
label {
|
|
||||||
class: "block text-gray-700 mb-2",
|
|
||||||
"Name"
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
class: "w-full px-3 py-2 border rounded-lg",
|
|
||||||
r#type: "text",
|
|
||||||
placeholder: "Your name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
label {
|
|
||||||
class: "block text-gray-700 mb-2",
|
|
||||||
"Email"
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
class: "w-full px-3 py-2 border rounded-lg",
|
|
||||||
r#type: "email",
|
|
||||||
placeholder: "Your email"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
label {
|
|
||||||
class: "block text-gray-700 mb-2",
|
|
||||||
"Message"
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
class: "w-full px-3 py-2 border rounded-lg h-32",
|
|
||||||
placeholder: "Your message"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
class: "bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700",
|
|
||||||
r#type: "submit",
|
|
||||||
"Send Message"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,199 @@
|
|||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use axum::response::Html;
|
||||||
use clap::Command;
|
use clap::Command;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use mize::Mize;
|
use mize::Mize;
|
||||||
use mize::MizeResult;
|
use mize::MizeResult;
|
||||||
|
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod main_page;
|
|
||||||
|
pub fn mc_page() -> Html<String> {
|
||||||
|
Html(r#"
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<title>PPC Minecraft Server</title>
|
||||||
|
<script defer data-domain="ppc.social" src="https://plausible.c2vi.dev/js/script.file-downloads.hash.outbound-links.pageview-props.revenue.tagged-events.js"></script>
|
||||||
|
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #f0efeb;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
padding: 100px;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
font-weight: 800;
|
||||||
|
color: #8e8e8e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strong { color: black; }
|
||||||
|
.semi { color: #484847; }
|
||||||
|
.link { color: #588fff; display: inline; }
|
||||||
|
.code { font-family: monospace; }
|
||||||
|
|
||||||
|
.mt { margin-top: 20px; }
|
||||||
|
.ml { margin-left: 20px; }
|
||||||
|
.mr { margin-right: 20px; }
|
||||||
|
.mb { margin-bottom: 20px; }
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 100%;
|
||||||
|
row-gap: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 20px;
|
||||||
|
flex: 1 0 400px;
|
||||||
|
& > * {
|
||||||
|
display: block;
|
||||||
|
margin-top: 5px;
|
||||||
|
.card-heading { font-size: 24px; }
|
||||||
|
.card-body {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 48px;
|
||||||
|
flex: 1 0 40%;
|
||||||
|
}
|
||||||
|
.info { flex: 1 0 60%; }
|
||||||
|
|
||||||
|
.sub-card {
|
||||||
|
flex: 1 1 50%;
|
||||||
|
height: min-content;
|
||||||
|
padding: 20px 20px 20px 0;
|
||||||
|
* {
|
||||||
|
display: block;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.card-heading { font-size: 18px; }
|
||||||
|
.card-footer { font-size: 12px; }
|
||||||
|
.card-body {
|
||||||
|
font-size: 23px;
|
||||||
|
color: #588fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 650px) {
|
||||||
|
* { word-wrap: normal; }
|
||||||
|
body { padding: 20px; }
|
||||||
|
.title { font-size: 24px; }
|
||||||
|
.card { padding: 0; flex: 0 0 100%; }
|
||||||
|
.card:not(:last-child):after {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
border-bottom: 5px solid currentColor;
|
||||||
|
margin: 20px 0 20px 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.card:last-child { margin-bottom: 100px; }
|
||||||
|
.sub-card:after { display: block; content: ""; border-bottom: 1px solid currentColor; margin-top: 20px; }
|
||||||
|
.main { row-gap: 0; }
|
||||||
|
.sub-card .card-body { font-size: 18px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 360px) {
|
||||||
|
body { font-size: 12px !important; }
|
||||||
|
.sub-card .card-body { font-size: 16px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="main flex">
|
||||||
|
<div class="card title">
|
||||||
|
<div>Welcome to the</div>
|
||||||
|
<div class="strong">PPC</div>
|
||||||
|
<div>Minecraft Cluster</div>
|
||||||
|
</div>
|
||||||
|
<div class="card info flex">
|
||||||
|
<div class="sub-card">
|
||||||
|
<span class="card-heading">Address</span>
|
||||||
|
<span class="card-body">first.ppc.social</span>
|
||||||
|
<span class="card-footer">Java 1.21.11 and Bedrock (bedrock not working rn)</span>
|
||||||
|
</div>
|
||||||
|
<div class="sub-card">
|
||||||
|
<span class="card-heading">Discord</span>
|
||||||
|
<a class="card-body" href="https://discord.gg/cMKQNJMkRe" target="_blank">https://discord.gg/cMKQNJMkRe</a>
|
||||||
|
<span class="card-footer">come and say hi!</span>
|
||||||
|
</div>
|
||||||
|
<div class="semi mt">
|
||||||
|
See you ingame and invite all your friends to play as well!!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<span class="card-heading strong">SimpleVoicechat</span>
|
||||||
|
<span class="card-body">
|
||||||
|
This is <span class="semi">not required</span>, but makes playing soooo much more fun, so it's
|
||||||
|
<span class="semi">highly encouraged</span>.
|
||||||
|
<br/><br/>
|
||||||
|
<span class="semi">Instructions can be found here</span>:<br/>
|
||||||
|
<a href="https://fabricmc.net/use/installer/" class="link ml">Fabric</a>
|
||||||
|
<br/>
|
||||||
|
<a class="link ml" href="https://modrepo.de/minecraft/voicechat/wiki/installation#fabric">SimpleVoiceChat</a>
|
||||||
|
<br/><br/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main_page() -> Html<String> {
|
||||||
|
Html(r#"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>People Corner</title>
|
||||||
|
<link rel="stylesheet" href="/app/static/styles.css">
|
||||||
|
<link rel="icon" type="image/png" href="/app/static/logo.png">
|
||||||
|
<script defer data-domain="ppc.social" src="https://plausible.c2vi.dev/js/script.file-downloads.hash.outbound-links.pageview-props.revenue.tagged-events.js"></script>
|
||||||
|
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body style="text-align: center" >
|
||||||
|
<div class="highlighted">
|
||||||
|
<h2>"Cool People doing Cool things together"</h2>
|
||||||
|
<p>Read what this is all about: <a href="https://wiki.ppc.social/u/c2vi/The-Idea-of-PPC">About</a></p>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<p>There is a Discord Server: <a href="https://discord.gg/cMKQNJMkRe">https://discord.gg/cMKQNJMkRe</a></p>
|
||||||
|
|
||||||
|
<p>There is a <a href="https://ppc.social/mc">Minecraft server</a>.</p>
|
||||||
|
|
||||||
|
<p>There is <a href="https://wiki.ppc.social">The PPC Wiki Projekt</a>.</p>
|
||||||
|
|
||||||
|
<p>There is an <a href="https://www.instagram.com/ppc.social/">Instagram</a> and a <a href="https://www.youtube.com/@ppc.social">Youtube Channel</a></p>
|
||||||
|
|
||||||
|
<p>and that's about all for now...</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#.to_owned())
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ use tower_sessions::{
|
|||||||
pub fn part_ppc_server(mize: &mut Mize) -> MizeResult<()> {
|
pub fn part_ppc_server(mize: &mut Mize) -> MizeResult<()> {
|
||||||
mize.add_name_only_part("ppc.server");
|
mize.add_name_only_part("ppc.server");
|
||||||
|
|
||||||
let mut mize_clone = mize.clone();
|
let mize_clone = mize.clone();
|
||||||
mize.spawn_async("ppc.server_runner", start_server(mize_clone));
|
mize.spawn_async("ppc.server_runner", start_server(mize_clone));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -38,9 +38,9 @@ pub fn part_ppc_server(mize: &mut Mize) -> MizeResult<()> {
|
|||||||
pub async fn start_server(mut mize: Mize) -> MizeResult<()> {
|
pub async fn start_server(mut mize: Mize) -> MizeResult<()> {
|
||||||
dioxus::logger::initialize_default();
|
dioxus::logger::initialize_default();
|
||||||
|
|
||||||
let issuer = mize.get_config("auth.issuer")?.value_string()?;
|
let issuer = mize.get_config("auth.server.issuer")?.value_string()?;
|
||||||
let client_id = mize.get_config("auth.client_id")?.value_string()?;
|
let client_id = mize.get_config("auth.server.client_id")?.value_string()?;
|
||||||
let client_secret = mize.get_config("auth.client_secret")?.value_string()?;
|
let client_secret = mize.get_config("auth.server.client_secret")?.value_string()?;
|
||||||
let url = mize.get_config("web.url")?.value_string()?;
|
let url = mize.get_config("web.url")?.value_string()?;
|
||||||
let redirect_url = format!("{url}/oidc");
|
let redirect_url = format!("{url}/oidc");
|
||||||
|
|
||||||
@@ -88,7 +88,8 @@ pub async fn start_server(mut mize: Mize) -> MizeResult<()> {
|
|||||||
.layer(session_layer)
|
.layer(session_layer)
|
||||||
.layer(tower_http::trace::TraceLayer::new_for_http())
|
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||||
// not authenticated routes
|
// not authenticated routes
|
||||||
.route("/", get(crate::ppc::main_page::main_page()))
|
.route("/", get(crate::ppc::main_page()))
|
||||||
|
.route("/mc", get(crate::ppc::mc_page()))
|
||||||
// matrix .well-known
|
// matrix .well-known
|
||||||
.route(
|
.route(
|
||||||
"/.well-known/matrix/client",
|
"/.well-known/matrix/client",
|
||||||
@@ -101,7 +102,7 @@ pub async fn start_server(mut mize: Mize) -> MizeResult<()> {
|
|||||||
|
|
||||||
tracing::info!("Running on http://localhost:3000");
|
tracing::info!("Running on http://localhost:3000");
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:8010").await.unwrap();
|
||||||
axum::serve(listener, app.into_make_service())
|
axum::serve(listener, app.into_make_service())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -140,6 +141,6 @@ async fn handle_matrix_client() -> Json<Value> {
|
|||||||
|
|
||||||
async fn handle_matrix_server() -> Json<Value> {
|
async fn handle_matrix_server() -> Json<Value> {
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"m.server": "://ppc.social"
|
"m.server": "matrix.ppc.social:443"
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
8
marts/src/sci/inbox/mod.rs
Normal file
8
marts/src/sci/inbox/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
pub use mize::{Mize, MizeResult, MizeError};
|
||||||
|
|
||||||
|
pub fn part_sci_inbox(mize: &mut Mize) -> MizeResult<()> {
|
||||||
|
|
||||||
|
mize.add_name_only_part("sci.inbox");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
use mize::*;
|
use mize::*;
|
||||||
pub mod timetrack;
|
pub mod timetrack;
|
||||||
|
use clap::Command;
|
||||||
|
|
||||||
pub fn part_sci(mize: &mut Mize) -> MizeResult<()> {
|
pub fn part_sci(mize: &mut Mize) -> MizeResult<()> {
|
||||||
part_add_thought(mize)?;
|
part_add_thought(mize)?;
|
||||||
@@ -9,20 +10,20 @@ pub fn part_sci(mize: &mut Mize) -> MizeResult<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn part_add_thought(mize: &mut Mize) -> MizeResult<()> {
|
pub fn part_add_thought(mize: &mut Mize) -> MizeResult<()> {
|
||||||
use marts::generated::spacetime::add_thought;
|
use crate::generated::spacetime::add_thought;
|
||||||
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?;
|
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
|
||||||
cli.set_command("sci");
|
cli.set_command("sci");
|
||||||
cli.subcommand(Command::new("inputThought").alias("ti"), |matches, mut mize| {
|
cli.subcommand(Command::new("inputThought").alias("ti"), |_matches, mut mize| {
|
||||||
|
|
||||||
let tmp_file = std::env::temp_dir().join(format!(
|
let tmp_file = std::env::temp_dir().join(format!(
|
||||||
"sc2-input-thought-{}.txt",
|
"sc2-input-thought-{}.txt",
|
||||||
chrono::Utc::now().timestamp()
|
chrono::Utc::now().timestamp()
|
||||||
));
|
));
|
||||||
marts::cli_spawn(format!("nvim {}", tmp_file.display()))?;
|
crate::cli_spawn(&mut mize, format!("nvim {}", tmp_file.display()))?;
|
||||||
let new_file_content = std::fs::read_to_string(&tmp_file)
|
let new_file_content = std::fs::read_to_string(&tmp_file)
|
||||||
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
|
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
|
||||||
let _ = std::fs::remove_file(&tmp_file);
|
let _ = std::fs::remove_file(&tmp_file);
|
||||||
let spacetime = mize.get_part_native::<marts::SpacetimePart>("spacetime")?;
|
let spacetime = mize.get_part_native::<crate::SpacetimePart>("spacetime")?;
|
||||||
spacetime.con().reducers.add_thought(new_file_content)?;
|
spacetime.con().reducers.add_thought(new_file_content)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ use mize::out;
|
|||||||
use mize::proto::MizeMessage;
|
use mize::proto::MizeMessage;
|
||||||
use mize::{Mize, MizeError, MizePart, MizePartCreate, MizeResult, mize_err, mize_part};
|
use mize::{Mize, MizeError, MizePart, MizePartCreate, MizeResult, mize_err, mize_part};
|
||||||
use spacetimedb_sdk::{DbContext, Table};
|
use spacetimedb_sdk::{DbContext, Table};
|
||||||
|
use generated::spacetime::export_timetrack;
|
||||||
|
use generated::spacetime::first_timetrack_date;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use chrono::Local;
|
||||||
|
use chrono::TimeDelta;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
use generated::spacetime::{
|
use generated::spacetime::{
|
||||||
Action, ActivitiesTableAccess, Activity, DbConnection, TaskPartTableAccess,
|
Action, ActivitiesTableAccess, Activity, DbConnection, TaskPartTableAccess,
|
||||||
@@ -149,7 +158,7 @@ impl MizePart for TimeTrack {
|
|||||||
|
|
||||||
#[cfg(feature = "target-os")]
|
#[cfg(feature = "target-os")]
|
||||||
fn command_next_action(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
fn command_next_action(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||||
let con = spacetime.con();
|
let con = spacetime.con();
|
||||||
|
|
||||||
let break_flag = matches.get_flag("break");
|
let break_flag = matches.get_flag("break");
|
||||||
@@ -183,7 +192,7 @@ fn command_next_action(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
|||||||
|
|
||||||
#[cfg(feature = "target-os")]
|
#[cfg(feature = "target-os")]
|
||||||
fn command_current_action(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
fn command_current_action(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||||
let action = get_current_action(&mut mize, spacetime.con())?;
|
let action = get_current_action(&mut mize, spacetime.con())?;
|
||||||
out!("{}", action.name);
|
out!("{}", action.name);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -220,7 +229,7 @@ fn get_current_action(_mize: &mut Mize, con: &DbConnection) -> MizeResult<Action
|
|||||||
|
|
||||||
#[cfg(feature = "target-os")]
|
#[cfg(feature = "target-os")]
|
||||||
fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||||
let activities = get_activities(&mut mize, spacetime.con())?;
|
let activities = get_activities(&mut mize, spacetime.con())?;
|
||||||
|
|
||||||
for ac in activities.clone() {
|
for ac in activities.clone() {
|
||||||
@@ -239,7 +248,7 @@ fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
|||||||
|
|
||||||
#[cfg(feature = "target-os")]
|
#[cfg(feature = "target-os")]
|
||||||
fn command_list_actions(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
fn command_list_actions(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||||
|
|
||||||
let actions = get_actions(spacetime.con())?;
|
let actions = get_actions(spacetime.con())?;
|
||||||
|
|
||||||
@@ -252,7 +261,7 @@ fn command_list_actions(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
|||||||
|
|
||||||
#[cfg(feature = "target-os")]
|
#[cfg(feature = "target-os")]
|
||||||
fn command_edit_action_list(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
fn command_edit_action_list(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||||
|
|
||||||
// maybe append a template to the action list
|
// maybe append a template to the action list
|
||||||
if matches.get_flag("defaultTemplate") {
|
if matches.get_flag("defaultTemplate") {
|
||||||
@@ -353,7 +362,7 @@ fn parse_action(action: &mut Action) {
|
|||||||
|
|
||||||
#[cfg(feature = "target-os")]
|
#[cfg(feature = "target-os")]
|
||||||
fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||||
|
|
||||||
// get list of activities
|
// get list of activities
|
||||||
let activities = get_activities(&mut mize, spacetime.con())?;
|
let activities = get_activities(&mut mize, spacetime.con())?;
|
||||||
@@ -546,18 +555,16 @@ fn parse_activities_from_text(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn part_timetrack_export(mize: &mut Mize) -> MizeResult<()> {
|
pub fn part_timetrack_export(mize: &mut Mize) -> MizeResult<()> {
|
||||||
use std::path::Path;
|
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
|
||||||
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?;
|
|
||||||
|
|
||||||
cli.set_command("ppc");
|
cli.set_command("sci");
|
||||||
cli.subcommand(Command::new("exportTimetrack").alias("et").arg(Arg::new("date").short('d')).arg(Arg::new("all").short('a').action(ArgAction::SetTrue)), |matches , mut mize| {
|
cli.subcommand(Command::new("timetrackExport").alias("te").arg(Arg::new("date").short('d')).arg(Arg::new("all").short('a').action(ArgAction::SetTrue)), |matches , mut mize| {
|
||||||
let spacetime = mize.get_part_native::<marts::SpacetimePart>("spacetime")?;
|
let spacetime = mize.get_part_native::<crate::SpacetimePart>("spacetime")?;
|
||||||
let notes_dir = mize.get_config("sc2.userNotesDir").unwrap_or_default().value_string()?;
|
|
||||||
let today = Local::now().format("%Y-%m-%d").to_string();
|
let today = Local::now().format("%Y-%m-%d").to_string();
|
||||||
let mut date = matches.get_one::<String>("date").unwrap_or(&today);
|
let mut date = matches.get_one::<String>("date").unwrap_or(&today);
|
||||||
std::fs::create_dir_all(Path::new(¬es_dir).join("timetrack_exports"))?;
|
|
||||||
let all = matches.get_flag("all");
|
let all = matches.get_flag("all");
|
||||||
if all {
|
if all {
|
||||||
|
out!("exporting all not implemented rn");
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
spacetime.con().procedures.first_timetrack_date_then("c2vi".to_string(), |_, res| {tx.send(res);});
|
spacetime.con().procedures.first_timetrack_date_then("c2vi".to_string(), |_, res| {tx.send(res);});
|
||||||
let res = rx.recv()???;
|
let res = rx.recv()???;
|
||||||
@@ -567,23 +574,56 @@ pub fn part_timetrack_export(mize: &mut Mize) -> MizeResult<()> {
|
|||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
spacetime.con().procedures.export_timetrack_then("c2vi".to_string(), date.to_owned(), |_, res| {tx.send(res);});
|
spacetime.con().procedures.export_timetrack_then("c2vi".to_string(), date.to_owned(), |_, res| {tx.send(res);});
|
||||||
let res = rx.recv()???;
|
let res = rx.recv()???;
|
||||||
let mut file = OpenOptions::new().create(true).write(true).open(Path::new(¬es_dir).join("timetrack_exports").join(format!("timetrack_export_{}.md", date)))?;
|
//let mut file = OpenOptions::new().create(true).write(true).open(Path::new(¬es_dir).join("timetrack_exports").join(format!("timetrack_export_{}.md", date)))?;
|
||||||
file.write_all(res.as_bytes())?;
|
//file.write_all(res.as_bytes())?;
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
out!("date: {}", date);
|
out!("date: {}", date);
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
spacetime.con().procedures.export_timetrack_then("c2vi".to_string(), date.to_owned(), |_, res| {tx.send(res);});
|
spacetime.con().procedures.export_timetrack_then("c2vi".to_string(), date.to_owned(), |_, res| {let _ = tx.send(res);});
|
||||||
let res = rx.recv()???;
|
let res = rx.recv()???;
|
||||||
let mut file = OpenOptions::new().create(true).write(true).open(Path::new(¬es_dir).join("timetrack_exports").join(format!("timetrack_export_{}.md", date)))?;
|
let daily_notes_path = mize.get_config("timetrack.dailyNotesPath")?.value_string()?;
|
||||||
file.write_all(res.as_bytes())?;
|
let note_path = format!("{}/{}.md", daily_notes_path, date);
|
||||||
|
out!("writing to file: {}", note_path);
|
||||||
|
write_under_heading(note_path, "## timetrack export", res.as_ref())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_under_heading(
|
||||||
|
file_path: impl AsRef<Path>,
|
||||||
|
heading: &str,
|
||||||
|
content: &str,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
|
||||||
|
let file_path = file_path.as_ref();
|
||||||
|
let markdown = fs::read_to_string(file_path)?;
|
||||||
|
|
||||||
|
let heading_line = format!("{}", heading);
|
||||||
|
let mut lines: Vec<&str> = markdown.lines().collect();
|
||||||
|
|
||||||
|
let index = lines
|
||||||
|
.iter()
|
||||||
|
.position(|line: &&str| line.trim() == heading_line)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::NotFound,
|
||||||
|
format!("Heading '{}' not found", heading),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
lines.insert(index + 1, content);
|
||||||
|
|
||||||
|
let mut updated = lines.join("\n");
|
||||||
|
updated.push('\n');
|
||||||
|
|
||||||
|
fs::write(file_path, updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn dates_until_today(start: &str) -> Result<Vec<String>, chrono::ParseError> {
|
fn dates_until_today(start: &str) -> Result<Vec<String>, chrono::ParseError> {
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
let mut current = NaiveDate::parse_from_str(start, "%Y-%m-%d")?;
|
let mut current = NaiveDate::parse_from_str(start, "%Y-%m-%d")?;
|
||||||
|
|||||||
Reference in New Issue
Block a user