deleted the old marts
This commit is contained in:
@@ -17,6 +17,9 @@ packages/
|
||||
└── macros/ # Mize macros
|
||||
```
|
||||
|
||||
## Important
|
||||
- usage of any git commands is strictly forbidden!
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
[package]
|
||||
name = "marts"
|
||||
description = "marts = mize_parts - parts collection of the Mize framework"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.59", optional = true, features = ["string"] }
|
||||
flume = "0.12.0"
|
||||
futures = "0.3.32"
|
||||
mize = { path = "../", default-features = false, features = [] }
|
||||
tokio = { version = "1.49.0", default-features = false, features = ["time"] }
|
||||
# https://github.com/rscarson/rustyscript/pull/415
|
||||
#rustyscript = { git = 'https://github.com/rscarson/rustyscript', rev = 'e39529eac2c5d37cfcbb7d7d63da7264d7a5afdb', optional = true }
|
||||
#deno_core = { version = "0.380.1", optional = true }
|
||||
reqwest = { version = "0.13.2", features = ["blocking", "json"] }
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.149"
|
||||
rusqlite = { version = "0.37", features = ["bundled"], optional = true }
|
||||
dioxus = { version = "0.7.1", optional = true }
|
||||
spacetimedb-sdk = { version = "2.6.0", default-features = false }
|
||||
generated = { path = "./generated" }
|
||||
webbrowser = { version = "1.2.0", optional = true }
|
||||
openidconnect = { version = "4", features = ["reqwest", "rustls-tls"], default-features = false }
|
||||
tracing = "0.1.40"
|
||||
wasm-bindgen = { version = "0.2.84", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.49", optional = true }
|
||||
web-sys = { version = "0.3.70", optional = true, features = [
|
||||
"Window",
|
||||
"Storage",
|
||||
"console"
|
||||
]}
|
||||
js-sys = { version = "0.3.70", optional = true }
|
||||
spacetimedb = { version = "2.6.0", features = ["unstable"] }
|
||||
log = "0.4"
|
||||
chrono = { version = "0.4.45", features = [], default-features = false }
|
||||
chrono-tz = { version = "0.10.4", default-features = false }
|
||||
url = "2.5.8"
|
||||
serenity = { version = "0.12.5", optional = true }
|
||||
matrix-sdk = "0.18.0"
|
||||
axum = { version = "0.8.9", optional = true }
|
||||
dioxus-ssr = { version = "0.7.10", optional = true }
|
||||
axum-oidc = { version = "=1.0.0-dev-1", optional = true }
|
||||
tower = { version = "0.5.3", optional = true }
|
||||
tower-sessions = { version = "0.15.0", optional = true }
|
||||
tower-http = { version = "0.7.1", optional = true, features = [ "cors", "trace" ] }
|
||||
cmd_lib = "2.0.1"
|
||||
|
||||
|
||||
|
||||
[features]
|
||||
default = ["target-os", "desktop", "server", "client"]
|
||||
#default = ["target-js", "target-obsidian"] # to get rust analyzer to analyzer js only parts
|
||||
target-spacetime = [ "mize/target-js", "spacetimedb-sdk/browser" ]
|
||||
target-os = [ "clap", "mize/target-os", "rusqlite", "dioxus", "dep:webbrowser" ]
|
||||
target-js = [ "mize/target-js", "spacetimedb-sdk/browser", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "js-sys", "generated/target-js", "web-sys/Storage", "web-sys/Window", "dioxus" ]
|
||||
target-obsidian = [ "target-js" ]
|
||||
desktop = ["dioxus/desktop"]
|
||||
client = []
|
||||
server = [ "axum", "dioxus-ssr", "axum-oidc", "tower", "tower-sessions", "tower-http" ]
|
||||
clap = ["dep:clap"]
|
||||
serenity = ["dep:serenity"]
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "@ppc/marts",
|
||||
"imports": {},
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"tasks": {}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import * as esbuild from "npm:esbuild";
|
||||
|
||||
import { denoPlugin } from "jsr:@deno/esbuild-plugin";
|
||||
|
||||
await esbuild.build({
|
||||
plugins: [denoPlugin()],
|
||||
entryPoints: ["./habitica.ts"],
|
||||
outfile: "./deno_dist/habitica.js",
|
||||
bundle: true,
|
||||
format: "iife",
|
||||
});
|
||||
esbuild.stop();
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "generated"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "generated"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
spacetimedb-sdk = "2.6.0"
|
||||
|
||||
[features]
|
||||
target-js = [ "spacetimedb-sdk/browser" ]
|
||||
@@ -1 +0,0 @@
|
||||
pub mod spacetime;
|
||||
@@ -1,60 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct Action {
|
||||
pub name: String,
|
||||
pub activity: Option<u64>,
|
||||
pub task: Option<u64>,
|
||||
pub start: Option<__sdk::Timestamp>,
|
||||
pub end: Option<__sdk::Timestamp>,
|
||||
pub duration: Option<u64>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for Action {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `Action`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct ActionCols {
|
||||
pub name: __sdk::__query_builder::Col<Action, String>,
|
||||
pub activity: __sdk::__query_builder::Col<Action, Option<u64>>,
|
||||
pub task: __sdk::__query_builder::Col<Action, Option<u64>>,
|
||||
pub start: __sdk::__query_builder::Col<Action, Option<__sdk::Timestamp>>,
|
||||
pub end: __sdk::__query_builder::Col<Action, Option<__sdk::Timestamp>>,
|
||||
pub duration: __sdk::__query_builder::Col<Action, Option<u64>>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for Action {
|
||||
type Cols = ActionCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
ActionCols {
|
||||
name: __sdk::__query_builder::Col::new(table_name, "name"),
|
||||
activity: __sdk::__query_builder::Col::new(table_name, "activity"),
|
||||
task: __sdk::__query_builder::Col::new(table_name, "task"),
|
||||
start: __sdk::__query_builder::Col::new(table_name, "start"),
|
||||
end: __sdk::__query_builder::Col::new(table_name, "end"),
|
||||
duration: __sdk::__query_builder::Col::new(table_name, "duration"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `Action`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct ActionIxCols {}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for Action {
|
||||
type IxCols = ActionIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
ActionIxCols {}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for Action {}
|
||||
@@ -1,159 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use super::activity_type::Activity;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `activities`.
|
||||
///
|
||||
/// Obtain a handle from the [`ActivitiesTableAccess::activities`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.activities()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.activities().on_insert(...)`.
|
||||
pub struct ActivitiesTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<Activity>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `activities`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait ActivitiesTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`ActivitiesTableHandle`], which mediates access to the table `activities`.
|
||||
fn activities(&self) -> ActivitiesTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl ActivitiesTableAccess for super::RemoteTables {
|
||||
fn activities(&self) -> ActivitiesTableHandle<'_> {
|
||||
ActivitiesTableHandle {
|
||||
imp: self.imp.get_table::<Activity>("activities"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ActivitiesInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct ActivitiesDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for ActivitiesTableHandle<'ctx> {
|
||||
type Row = Activity;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = Activity> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = ActivitiesInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ActivitiesInsertCallbackId {
|
||||
ActivitiesInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: ActivitiesInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = ActivitiesDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ActivitiesDeleteCallbackId {
|
||||
ActivitiesDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: ActivitiesDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ActivitiesUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for ActivitiesTableHandle<'ctx> {
|
||||
type UpdateCallbackId = ActivitiesUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> ActivitiesUpdateCallbackId {
|
||||
ActivitiesUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: ActivitiesUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `id` unique index on the table `activities`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`ActivitiesIdUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.activities().id().find(...)`.
|
||||
pub struct ActivitiesIdUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<Activity, u64>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> ActivitiesTableHandle<'ctx> {
|
||||
/// Get a handle on the `id` unique index on the table `activities`.
|
||||
pub fn id(&self) -> ActivitiesIdUnique<'ctx> {
|
||||
ActivitiesIdUnique {
|
||||
imp: self.imp.get_unique_constraint::<u64>("id"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ActivitiesIdUnique<'ctx> {
|
||||
/// Find the subscribed row whose `id` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &u64) -> Option<Activity> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table = client_cache.get_or_make_table::<Activity>("activities");
|
||||
_table.add_unique_constraint::<u64>("id", |row| &row.id);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<Activity>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse("TableUpdate<Activity>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `Activity`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait activitiesQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `Activity`.
|
||||
fn activities(&self) -> __sdk::__query_builder::Table<Activity>;
|
||||
}
|
||||
|
||||
impl activitiesQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn activities(&self) -> __sdk::__query_builder::Table<Activity> {
|
||||
__sdk::__query_builder::Table::new("activities")
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct Activity {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub task: Option<u64>,
|
||||
pub parents_ids: Vec<u64>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for Activity {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `Activity`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct ActivityCols {
|
||||
pub id: __sdk::__query_builder::Col<Activity, u64>,
|
||||
pub name: __sdk::__query_builder::Col<Activity, String>,
|
||||
pub task: __sdk::__query_builder::Col<Activity, Option<u64>>,
|
||||
pub parents_ids: __sdk::__query_builder::Col<Activity, Vec<u64>>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for Activity {
|
||||
type Cols = ActivityCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
ActivityCols {
|
||||
id: __sdk::__query_builder::Col::new(table_name, "id"),
|
||||
name: __sdk::__query_builder::Col::new(table_name, "name"),
|
||||
task: __sdk::__query_builder::Col::new(table_name, "task"),
|
||||
parents_ids: __sdk::__query_builder::Col::new(table_name, "parents_ids"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `Activity`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct ActivityIxCols {
|
||||
pub id: __sdk::__query_builder::IxCol<Activity, u64>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for Activity {
|
||||
type IxCols = ActivityIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
ActivityIxCols {
|
||||
id: __sdk::__query_builder::IxCol::new(table_name, "id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for Activity {}
|
||||
@@ -1,66 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub(super) struct AddActivityArgs {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl From<AddActivityArgs> for super::Reducer {
|
||||
fn from(args: AddActivityArgs) -> Self {
|
||||
Self::AddActivity { name: args.name }
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AddActivityArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the reducer `add_activity`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteReducers`].
|
||||
pub trait add_activity {
|
||||
/// Request that the remote module invoke the reducer `add_activity` to run as soon as possible.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`add_activity:add_activity_then`] to run a callback after the reducer completes.
|
||||
fn add_activity(&self, name: String) -> __sdk::Result<()> {
|
||||
self.add_activity_then(name, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `add_activity` to run as soon as possible,
|
||||
/// registering `callback` to run when we are notified that the reducer completed.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and its status can be observed with the `callback`.
|
||||
fn add_activity_then(
|
||||
&self,
|
||||
name: String,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
impl add_activity for super::RemoteReducers {
|
||||
fn add_activity_then(
|
||||
&self,
|
||||
name: String,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(AddActivityArgs { name }, callback)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub(super) struct AddThoughtArgs {
|
||||
pub thought: String,
|
||||
}
|
||||
|
||||
impl From<AddThoughtArgs> for super::Reducer {
|
||||
fn from(args: AddThoughtArgs) -> Self {
|
||||
Self::AddThought {
|
||||
thought: args.thought,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AddThoughtArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the reducer `add_thought`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteReducers`].
|
||||
pub trait add_thought {
|
||||
/// Request that the remote module invoke the reducer `add_thought` to run as soon as possible.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`add_thought:add_thought_then`] to run a callback after the reducer completes.
|
||||
fn add_thought(&self, thought: String) -> __sdk::Result<()> {
|
||||
self.add_thought_then(thought, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `add_thought` to run as soon as possible,
|
||||
/// registering `callback` to run when we are notified that the reducer completed.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and its status can be observed with the `callback`.
|
||||
fn add_thought_then(
|
||||
&self,
|
||||
thought: String,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
impl add_thought for super::RemoteReducers {
|
||||
fn add_thought_then(
|
||||
&self,
|
||||
thought: String,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(AddThoughtArgs { thought }, callback)
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use super::daily_task_log_type::DailyTaskLog;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `daily_task_log`.
|
||||
///
|
||||
/// Obtain a handle from the [`DailyTaskLogTableAccess::daily_task_log`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.daily_task_log()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.daily_task_log().on_insert(...)`.
|
||||
pub struct DailyTaskLogTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<DailyTaskLog>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `daily_task_log`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait DailyTaskLogTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`DailyTaskLogTableHandle`], which mediates access to the table `daily_task_log`.
|
||||
fn daily_task_log(&self) -> DailyTaskLogTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl DailyTaskLogTableAccess for super::RemoteTables {
|
||||
fn daily_task_log(&self) -> DailyTaskLogTableHandle<'_> {
|
||||
DailyTaskLogTableHandle {
|
||||
imp: self.imp.get_table::<DailyTaskLog>("daily_task_log"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DailyTaskLogInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct DailyTaskLogDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for DailyTaskLogTableHandle<'ctx> {
|
||||
type Row = DailyTaskLog;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = DailyTaskLog> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = DailyTaskLogInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> DailyTaskLogInsertCallbackId {
|
||||
DailyTaskLogInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: DailyTaskLogInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = DailyTaskLogDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> DailyTaskLogDeleteCallbackId {
|
||||
DailyTaskLogDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: DailyTaskLogDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table = client_cache.get_or_make_table::<DailyTaskLog>("daily_task_log");
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<DailyTaskLog>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse("TableUpdate<DailyTaskLog>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `DailyTaskLog`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait daily_task_logQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `DailyTaskLog`.
|
||||
fn daily_task_log(&self) -> __sdk::__query_builder::Table<DailyTaskLog>;
|
||||
}
|
||||
|
||||
impl daily_task_logQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn daily_task_log(&self) -> __sdk::__query_builder::Table<DailyTaskLog> {
|
||||
__sdk::__query_builder::Table::new("daily_task_log")
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct DailyTaskLog {
|
||||
pub daily_task_id: i64,
|
||||
pub done: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for DailyTaskLog {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `DailyTaskLog`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct DailyTaskLogCols {
|
||||
pub daily_task_id: __sdk::__query_builder::Col<DailyTaskLog, i64>,
|
||||
pub done: __sdk::__query_builder::Col<DailyTaskLog, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for DailyTaskLog {
|
||||
type Cols = DailyTaskLogCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
DailyTaskLogCols {
|
||||
daily_task_id: __sdk::__query_builder::Col::new(table_name, "daily_task_id"),
|
||||
done: __sdk::__query_builder::Col::new(table_name, "done"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `DailyTaskLog`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct DailyTaskLogIxCols {}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for DailyTaskLog {
|
||||
type IxCols = DailyTaskLogIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
DailyTaskLogIxCols {}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for DailyTaskLog {}
|
||||
@@ -1,111 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use super::daily_task_type::DailyTask;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `daily_task`.
|
||||
///
|
||||
/// Obtain a handle from the [`DailyTaskTableAccess::daily_task`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.daily_task()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.daily_task().on_insert(...)`.
|
||||
pub struct DailyTaskTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<DailyTask>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `daily_task`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait DailyTaskTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`DailyTaskTableHandle`], which mediates access to the table `daily_task`.
|
||||
fn daily_task(&self) -> DailyTaskTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl DailyTaskTableAccess for super::RemoteTables {
|
||||
fn daily_task(&self) -> DailyTaskTableHandle<'_> {
|
||||
DailyTaskTableHandle {
|
||||
imp: self.imp.get_table::<DailyTask>("daily_task"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DailyTaskInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct DailyTaskDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for DailyTaskTableHandle<'ctx> {
|
||||
type Row = DailyTask;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = DailyTask> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = DailyTaskInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> DailyTaskInsertCallbackId {
|
||||
DailyTaskInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: DailyTaskInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = DailyTaskDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> DailyTaskDeleteCallbackId {
|
||||
DailyTaskDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: DailyTaskDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table = client_cache.get_or_make_table::<DailyTask>("daily_task");
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<DailyTask>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse("TableUpdate<DailyTask>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `DailyTask`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait daily_taskQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `DailyTask`.
|
||||
fn daily_task(&self) -> __sdk::__query_builder::Table<DailyTask>;
|
||||
}
|
||||
|
||||
impl daily_taskQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn daily_task(&self) -> __sdk::__query_builder::Table<DailyTask> {
|
||||
__sdk::__query_builder::Table::new("daily_task")
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct DailyTask {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub owner: __sdk::Identity,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for DailyTask {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `DailyTask`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct DailyTaskCols {
|
||||
pub id: __sdk::__query_builder::Col<DailyTask, i64>,
|
||||
pub name: __sdk::__query_builder::Col<DailyTask, String>,
|
||||
pub owner: __sdk::__query_builder::Col<DailyTask, __sdk::Identity>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for DailyTask {
|
||||
type Cols = DailyTaskCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
DailyTaskCols {
|
||||
id: __sdk::__query_builder::Col::new(table_name, "id"),
|
||||
name: __sdk::__query_builder::Col::new(table_name, "name"),
|
||||
owner: __sdk::__query_builder::Col::new(table_name, "owner"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `DailyTask`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct DailyTaskIxCols {}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for DailyTask {
|
||||
type IxCols = DailyTaskIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
DailyTaskIxCols {}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for DailyTask {}
|
||||
@@ -1,59 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct ExportTimetrackArgs {
|
||||
pub username: String,
|
||||
pub date: String,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for ExportTimetrackArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `export_timetrack`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait export_timetrack {
|
||||
fn export_timetrack(&self, username: String, date: String) {
|
||||
self.export_timetrack_then(username, date, |_, _| {});
|
||||
}
|
||||
|
||||
fn export_timetrack_then(
|
||||
&self,
|
||||
username: String,
|
||||
date: String,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<Result<String, String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl export_timetrack for super::RemoteProcedures {
|
||||
fn export_timetrack_then(
|
||||
&self,
|
||||
username: String,
|
||||
date: String,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<Result<String, String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, Result<String, String>>(
|
||||
"export_timetrack",
|
||||
ExportTimetrackArgs { username, date },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct FirstTimetrackDateArgs {
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for FirstTimetrackDateArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `first_timetrack_date`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait first_timetrack_date {
|
||||
fn first_timetrack_date(&self, username: String) {
|
||||
self.first_timetrack_date_then(username, |_, _| {});
|
||||
}
|
||||
|
||||
fn first_timetrack_date_then(
|
||||
&self,
|
||||
username: String,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<Result<String, String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl first_timetrack_date for super::RemoteProcedures {
|
||||
fn first_timetrack_date_then(
|
||||
&self,
|
||||
username: String,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<Result<String, String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, Result<String, String>>(
|
||||
"first_timetrack_date",
|
||||
FirstTimetrackDateArgs { username },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::action_type::Action;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct LogEvent {
|
||||
pub id: u64,
|
||||
pub start: __sdk::Timestamp,
|
||||
pub end: __sdk::Timestamp,
|
||||
pub action: Action,
|
||||
pub user: __sdk::Identity,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for LogEvent {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `LogEvent`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct LogEventCols {
|
||||
pub id: __sdk::__query_builder::Col<LogEvent, u64>,
|
||||
pub start: __sdk::__query_builder::Col<LogEvent, __sdk::Timestamp>,
|
||||
pub end: __sdk::__query_builder::Col<LogEvent, __sdk::Timestamp>,
|
||||
pub action: __sdk::__query_builder::Col<LogEvent, Action>,
|
||||
pub user: __sdk::__query_builder::Col<LogEvent, __sdk::Identity>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for LogEvent {
|
||||
type Cols = LogEventCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
LogEventCols {
|
||||
id: __sdk::__query_builder::Col::new(table_name, "id"),
|
||||
start: __sdk::__query_builder::Col::new(table_name, "start"),
|
||||
end: __sdk::__query_builder::Col::new(table_name, "end"),
|
||||
action: __sdk::__query_builder::Col::new(table_name, "action"),
|
||||
user: __sdk::__query_builder::Col::new(table_name, "user"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `LogEvent`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct LogEventIxCols {
|
||||
pub id: __sdk::__query_builder::IxCol<LogEvent, u64>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for LogEvent {
|
||||
type IxCols = LogEventIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
LogEventIxCols {
|
||||
id: __sdk::__query_builder::IxCol::new(table_name, "id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for LogEvent {}
|
||||
@@ -1,160 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use super::action_type::Action;
|
||||
use super::log_event_type::LogEvent;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `log_events`.
|
||||
///
|
||||
/// Obtain a handle from the [`LogEventsTableAccess::log_events`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.log_events()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.log_events().on_insert(...)`.
|
||||
pub struct LogEventsTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<LogEvent>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `log_events`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait LogEventsTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`LogEventsTableHandle`], which mediates access to the table `log_events`.
|
||||
fn log_events(&self) -> LogEventsTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl LogEventsTableAccess for super::RemoteTables {
|
||||
fn log_events(&self) -> LogEventsTableHandle<'_> {
|
||||
LogEventsTableHandle {
|
||||
imp: self.imp.get_table::<LogEvent>("log_events"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LogEventsInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct LogEventsDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for LogEventsTableHandle<'ctx> {
|
||||
type Row = LogEvent;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = LogEvent> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = LogEventsInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> LogEventsInsertCallbackId {
|
||||
LogEventsInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: LogEventsInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = LogEventsDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> LogEventsDeleteCallbackId {
|
||||
LogEventsDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: LogEventsDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LogEventsUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for LogEventsTableHandle<'ctx> {
|
||||
type UpdateCallbackId = LogEventsUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> LogEventsUpdateCallbackId {
|
||||
LogEventsUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: LogEventsUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `id` unique index on the table `log_events`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`LogEventsIdUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.log_events().id().find(...)`.
|
||||
pub struct LogEventsIdUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<LogEvent, u64>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> LogEventsTableHandle<'ctx> {
|
||||
/// Get a handle on the `id` unique index on the table `log_events`.
|
||||
pub fn id(&self) -> LogEventsIdUnique<'ctx> {
|
||||
LogEventsIdUnique {
|
||||
imp: self.imp.get_unique_constraint::<u64>("id"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> LogEventsIdUnique<'ctx> {
|
||||
/// Find the subscribed row whose `id` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &u64) -> Option<LogEvent> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table = client_cache.get_or_make_table::<LogEvent>("log_events");
|
||||
_table.add_unique_constraint::<u64>("id", |row| &row.id);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<LogEvent>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse("TableUpdate<LogEvent>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `LogEvent`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait log_eventsQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `LogEvent`.
|
||||
fn log_events(&self) -> __sdk::__query_builder::Table<LogEvent>;
|
||||
}
|
||||
|
||||
impl log_eventsQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn log_events(&self) -> __sdk::__query_builder::Table<LogEvent> {
|
||||
__sdk::__query_builder::Table::new("log_events")
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct MigrateArgs {}
|
||||
|
||||
impl __sdk::InModule for MigrateArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `migrate`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait migrate {
|
||||
fn migrate(&self) {
|
||||
self.migrate_then(|_, _| {});
|
||||
}
|
||||
|
||||
fn migrate_then(
|
||||
&self,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl migrate for super::RemoteProcedures {
|
||||
fn migrate_then(
|
||||
&self,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, String>("migrate", MigrateArgs {}, __callback);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,74 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::action_type::Action;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub(super) struct NextActionArgs {
|
||||
pub break_flag: bool,
|
||||
pub action: Option<Action>,
|
||||
}
|
||||
|
||||
impl From<NextActionArgs> for super::Reducer {
|
||||
fn from(args: NextActionArgs) -> Self {
|
||||
Self::NextAction {
|
||||
break_flag: args.break_flag,
|
||||
action: args.action,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for NextActionArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the reducer `next_action`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteReducers`].
|
||||
pub trait next_action {
|
||||
/// Request that the remote module invoke the reducer `next_action` to run as soon as possible.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`next_action:next_action_then`] to run a callback after the reducer completes.
|
||||
fn next_action(&self, break_flag: bool, action: Option<Action>) -> __sdk::Result<()> {
|
||||
self.next_action_then(break_flag, action, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `next_action` to run as soon as possible,
|
||||
/// registering `callback` to run when we are notified that the reducer completed.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and its status can be observed with the `callback`.
|
||||
fn next_action_then(
|
||||
&self,
|
||||
break_flag: bool,
|
||||
action: Option<Action>,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
impl next_action for super::RemoteReducers {
|
||||
fn next_action_then(
|
||||
&self,
|
||||
break_flag: bool,
|
||||
action: Option<Action>,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(NextActionArgs { break_flag, action }, callback)
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct Profile {
|
||||
pub identity: __sdk::Identity,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for Profile {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `Profile`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct ProfileCols {
|
||||
pub identity: __sdk::__query_builder::Col<Profile, __sdk::Identity>,
|
||||
pub username: __sdk::__query_builder::Col<Profile, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for Profile {
|
||||
type Cols = ProfileCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
ProfileCols {
|
||||
identity: __sdk::__query_builder::Col::new(table_name, "identity"),
|
||||
username: __sdk::__query_builder::Col::new(table_name, "username"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `Profile`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct ProfileIxCols {
|
||||
pub identity: __sdk::__query_builder::IxCol<Profile, __sdk::Identity>,
|
||||
pub username: __sdk::__query_builder::IxCol<Profile, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for Profile {
|
||||
type IxCols = ProfileIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
ProfileIxCols {
|
||||
identity: __sdk::__query_builder::IxCol::new(table_name, "identity"),
|
||||
username: __sdk::__query_builder::IxCol::new(table_name, "username"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for Profile {}
|
||||
@@ -1,192 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use super::profile_type::Profile;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `profiles`.
|
||||
///
|
||||
/// Obtain a handle from the [`ProfilesTableAccess::profiles`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.profiles()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profiles().on_insert(...)`.
|
||||
pub struct ProfilesTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<Profile>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `profiles`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait ProfilesTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`ProfilesTableHandle`], which mediates access to the table `profiles`.
|
||||
fn profiles(&self) -> ProfilesTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl ProfilesTableAccess for super::RemoteTables {
|
||||
fn profiles(&self) -> ProfilesTableHandle<'_> {
|
||||
ProfilesTableHandle {
|
||||
imp: self.imp.get_table::<Profile>("profiles"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfilesInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct ProfilesDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for ProfilesTableHandle<'ctx> {
|
||||
type Row = Profile;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = Profile> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = ProfilesInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfilesInsertCallbackId {
|
||||
ProfilesInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: ProfilesInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = ProfilesDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfilesDeleteCallbackId {
|
||||
ProfilesDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: ProfilesDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfilesUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for ProfilesTableHandle<'ctx> {
|
||||
type UpdateCallbackId = ProfilesUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> ProfilesUpdateCallbackId {
|
||||
ProfilesUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: ProfilesUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `identity` unique index on the table `profiles`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`ProfilesIdentityUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profiles().identity().find(...)`.
|
||||
pub struct ProfilesIdentityUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<Profile, __sdk::Identity>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> ProfilesTableHandle<'ctx> {
|
||||
/// Get a handle on the `identity` unique index on the table `profiles`.
|
||||
pub fn identity(&self) -> ProfilesIdentityUnique<'ctx> {
|
||||
ProfilesIdentityUnique {
|
||||
imp: self
|
||||
.imp
|
||||
.get_unique_constraint::<__sdk::Identity>("identity"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProfilesIdentityUnique<'ctx> {
|
||||
/// Find the subscribed row whose `identity` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &__sdk::Identity) -> Option<Profile> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `username` unique index on the table `profiles`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`ProfilesUsernameUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profiles().username().find(...)`.
|
||||
pub struct ProfilesUsernameUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<Profile, String>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> ProfilesTableHandle<'ctx> {
|
||||
/// Get a handle on the `username` unique index on the table `profiles`.
|
||||
pub fn username(&self) -> ProfilesUsernameUnique<'ctx> {
|
||||
ProfilesUsernameUnique {
|
||||
imp: self.imp.get_unique_constraint::<String>("username"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProfilesUsernameUnique<'ctx> {
|
||||
/// Find the subscribed row whose `username` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &String) -> Option<Profile> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table = client_cache.get_or_make_table::<Profile>("profiles");
|
||||
_table.add_unique_constraint::<__sdk::Identity>("identity", |row| &row.identity);
|
||||
_table.add_unique_constraint::<String>("username", |row| &row.username);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<Profile>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse("TableUpdate<Profile>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `Profile`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait profilesQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `Profile`.
|
||||
fn profiles(&self) -> __sdk::__query_builder::Table<Profile>;
|
||||
}
|
||||
|
||||
impl profilesQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn profiles(&self) -> __sdk::__query_builder::Table<Profile> {
|
||||
__sdk::__query_builder::Table::new("profiles")
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::activity_type::Activity;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub(super) struct RemoveActivityArgs {
|
||||
pub activity: Activity,
|
||||
}
|
||||
|
||||
impl From<RemoveActivityArgs> for super::Reducer {
|
||||
fn from(args: RemoveActivityArgs) -> Self {
|
||||
Self::RemoveActivity {
|
||||
activity: args.activity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RemoveActivityArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the reducer `remove_activity`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteReducers`].
|
||||
pub trait remove_activity {
|
||||
/// Request that the remote module invoke the reducer `remove_activity` to run as soon as possible.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`remove_activity:remove_activity_then`] to run a callback after the reducer completes.
|
||||
fn remove_activity(&self, activity: Activity) -> __sdk::Result<()> {
|
||||
self.remove_activity_then(activity, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `remove_activity` to run as soon as possible,
|
||||
/// registering `callback` to run when we are notified that the reducer completed.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and its status can be observed with the `callback`.
|
||||
fn remove_activity_then(
|
||||
&self,
|
||||
activity: Activity,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
impl remove_activity for super::RemoteReducers {
|
||||
fn remove_activity_then(
|
||||
&self,
|
||||
activity: Activity,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(RemoveActivityArgs { activity }, callback)
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub(super) struct SetActivityNameArgs {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl From<SetActivityNameArgs> for super::Reducer {
|
||||
fn from(args: SetActivityNameArgs) -> Self {
|
||||
Self::SetActivityName {
|
||||
id: args.id,
|
||||
name: args.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for SetActivityNameArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the reducer `set_activity_name`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteReducers`].
|
||||
pub trait set_activity_name {
|
||||
/// Request that the remote module invoke the reducer `set_activity_name` to run as soon as possible.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`set_activity_name:set_activity_name_then`] to run a callback after the reducer completes.
|
||||
fn set_activity_name(&self, id: u64, name: String) -> __sdk::Result<()> {
|
||||
self.set_activity_name_then(id, name, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `set_activity_name` to run as soon as possible,
|
||||
/// registering `callback` to run when we are notified that the reducer completed.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and its status can be observed with the `callback`.
|
||||
fn set_activity_name_then(
|
||||
&self,
|
||||
id: u64,
|
||||
name: String,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
impl set_activity_name for super::RemoteReducers {
|
||||
fn set_activity_name_then(
|
||||
&self,
|
||||
id: u64,
|
||||
name: String,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(SetActivityNameArgs { id, name }, callback)
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use super::action_type::Action;
|
||||
use super::task_part_type::TaskPart;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `task_part`.
|
||||
///
|
||||
/// Obtain a handle from the [`TaskPartTableAccess::task_part`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.task_part()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.task_part().on_insert(...)`.
|
||||
pub struct TaskPartTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<TaskPart>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `task_part`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait TaskPartTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`TaskPartTableHandle`], which mediates access to the table `task_part`.
|
||||
fn task_part(&self) -> TaskPartTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl TaskPartTableAccess for super::RemoteTables {
|
||||
fn task_part(&self) -> TaskPartTableHandle<'_> {
|
||||
TaskPartTableHandle {
|
||||
imp: self.imp.get_table::<TaskPart>("task_part"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TaskPartInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct TaskPartDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for TaskPartTableHandle<'ctx> {
|
||||
type Row = TaskPart;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = TaskPart> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = TaskPartInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> TaskPartInsertCallbackId {
|
||||
TaskPartInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: TaskPartInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = TaskPartDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> TaskPartDeleteCallbackId {
|
||||
TaskPartDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: TaskPartDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TaskPartUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for TaskPartTableHandle<'ctx> {
|
||||
type UpdateCallbackId = TaskPartUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> TaskPartUpdateCallbackId {
|
||||
TaskPartUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: TaskPartUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `identity` unique index on the table `task_part`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`TaskPartIdentityUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.task_part().identity().find(...)`.
|
||||
pub struct TaskPartIdentityUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<TaskPart, __sdk::Identity>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> TaskPartTableHandle<'ctx> {
|
||||
/// Get a handle on the `identity` unique index on the table `task_part`.
|
||||
pub fn identity(&self) -> TaskPartIdentityUnique<'ctx> {
|
||||
TaskPartIdentityUnique {
|
||||
imp: self
|
||||
.imp
|
||||
.get_unique_constraint::<__sdk::Identity>("identity"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> TaskPartIdentityUnique<'ctx> {
|
||||
/// Find the subscribed row whose `identity` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &__sdk::Identity) -> Option<TaskPart> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table = client_cache.get_or_make_table::<TaskPart>("task_part");
|
||||
_table.add_unique_constraint::<__sdk::Identity>("identity", |row| &row.identity);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<TaskPart>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse("TableUpdate<TaskPart>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `TaskPart`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait task_partQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `TaskPart`.
|
||||
fn task_part(&self) -> __sdk::__query_builder::Table<TaskPart>;
|
||||
}
|
||||
|
||||
impl task_partQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn task_part(&self) -> __sdk::__query_builder::Table<TaskPart> {
|
||||
__sdk::__query_builder::Table::new("task_part")
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::action_type::Action;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct TaskPart {
|
||||
pub identity: __sdk::Identity,
|
||||
pub start_of_current: __sdk::Timestamp,
|
||||
pub current: Action,
|
||||
pub upcoming: Vec<Action>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for TaskPart {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `TaskPart`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct TaskPartCols {
|
||||
pub identity: __sdk::__query_builder::Col<TaskPart, __sdk::Identity>,
|
||||
pub start_of_current: __sdk::__query_builder::Col<TaskPart, __sdk::Timestamp>,
|
||||
pub current: __sdk::__query_builder::Col<TaskPart, Action>,
|
||||
pub upcoming: __sdk::__query_builder::Col<TaskPart, Vec<Action>>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for TaskPart {
|
||||
type Cols = TaskPartCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
TaskPartCols {
|
||||
identity: __sdk::__query_builder::Col::new(table_name, "identity"),
|
||||
start_of_current: __sdk::__query_builder::Col::new(table_name, "start_of_current"),
|
||||
current: __sdk::__query_builder::Col::new(table_name, "current"),
|
||||
upcoming: __sdk::__query_builder::Col::new(table_name, "upcoming"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `TaskPart`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct TaskPartIxCols {
|
||||
pub identity: __sdk::__query_builder::IxCol<TaskPart, __sdk::Identity>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for TaskPart {
|
||||
type IxCols = TaskPartIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
TaskPartIxCols {
|
||||
identity: __sdk::__query_builder::IxCol::new(table_name, "identity"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for TaskPart {}
|
||||
@@ -1,58 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct Task {
|
||||
pub id: u64,
|
||||
pub done: bool,
|
||||
pub name: String,
|
||||
pub parent: Option<u64>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for Task {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `Task`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct TaskCols {
|
||||
pub id: __sdk::__query_builder::Col<Task, u64>,
|
||||
pub done: __sdk::__query_builder::Col<Task, bool>,
|
||||
pub name: __sdk::__query_builder::Col<Task, String>,
|
||||
pub parent: __sdk::__query_builder::Col<Task, Option<u64>>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for Task {
|
||||
type Cols = TaskCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
TaskCols {
|
||||
id: __sdk::__query_builder::Col::new(table_name, "id"),
|
||||
done: __sdk::__query_builder::Col::new(table_name, "done"),
|
||||
name: __sdk::__query_builder::Col::new(table_name, "name"),
|
||||
parent: __sdk::__query_builder::Col::new(table_name, "parent"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `Task`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct TaskIxCols {
|
||||
pub id: __sdk::__query_builder::IxCol<Task, u64>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for Task {
|
||||
type IxCols = TaskIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
TaskIxCols {
|
||||
id: __sdk::__query_builder::IxCol::new(table_name, "id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for Task {}
|
||||
@@ -1,159 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use super::task_type::Task;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `tasks`.
|
||||
///
|
||||
/// Obtain a handle from the [`TasksTableAccess::tasks`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.tasks()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.tasks().on_insert(...)`.
|
||||
pub struct TasksTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<Task>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `tasks`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait TasksTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`TasksTableHandle`], which mediates access to the table `tasks`.
|
||||
fn tasks(&self) -> TasksTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl TasksTableAccess for super::RemoteTables {
|
||||
fn tasks(&self) -> TasksTableHandle<'_> {
|
||||
TasksTableHandle {
|
||||
imp: self.imp.get_table::<Task>("tasks"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TasksInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct TasksDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for TasksTableHandle<'ctx> {
|
||||
type Row = Task;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = Task> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = TasksInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> TasksInsertCallbackId {
|
||||
TasksInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: TasksInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = TasksDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> TasksDeleteCallbackId {
|
||||
TasksDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: TasksDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TasksUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for TasksTableHandle<'ctx> {
|
||||
type UpdateCallbackId = TasksUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> TasksUpdateCallbackId {
|
||||
TasksUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: TasksUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `id` unique index on the table `tasks`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`TasksIdUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.tasks().id().find(...)`.
|
||||
pub struct TasksIdUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<Task, u64>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> TasksTableHandle<'ctx> {
|
||||
/// Get a handle on the `id` unique index on the table `tasks`.
|
||||
pub fn id(&self) -> TasksIdUnique<'ctx> {
|
||||
TasksIdUnique {
|
||||
imp: self.imp.get_unique_constraint::<u64>("id"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> TasksIdUnique<'ctx> {
|
||||
/// Find the subscribed row whose `id` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &u64) -> Option<Task> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table = client_cache.get_or_make_table::<Task>("tasks");
|
||||
_table.add_unique_constraint::<u64>("id", |row| &row.id);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<Task>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse("TableUpdate<Task>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `Task`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait tasksQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `Task`.
|
||||
fn tasks(&self) -> __sdk::__query_builder::Table<Task>;
|
||||
}
|
||||
|
||||
impl tasksQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn tasks(&self) -> __sdk::__query_builder::Table<Task> {
|
||||
__sdk::__query_builder::Table::new("tasks")
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct TestArgs {}
|
||||
|
||||
impl __sdk::InModule for TestArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `test`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait test {
|
||||
fn test(&self) {
|
||||
self.test_then(|_, _| {});
|
||||
}
|
||||
|
||||
fn test_then(
|
||||
&self,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl test for super::RemoteProcedures {
|
||||
fn test_then(
|
||||
&self,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, String>("test", TestArgs {}, __callback);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct Thought {
|
||||
pub id: u64,
|
||||
pub text: String,
|
||||
pub user: __sdk::Identity,
|
||||
pub time: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for Thought {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `Thought`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct ThoughtCols {
|
||||
pub id: __sdk::__query_builder::Col<Thought, u64>,
|
||||
pub text: __sdk::__query_builder::Col<Thought, String>,
|
||||
pub user: __sdk::__query_builder::Col<Thought, __sdk::Identity>,
|
||||
pub time: __sdk::__query_builder::Col<Thought, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for Thought {
|
||||
type Cols = ThoughtCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
ThoughtCols {
|
||||
id: __sdk::__query_builder::Col::new(table_name, "id"),
|
||||
text: __sdk::__query_builder::Col::new(table_name, "text"),
|
||||
user: __sdk::__query_builder::Col::new(table_name, "user"),
|
||||
time: __sdk::__query_builder::Col::new(table_name, "time"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `Thought`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct ThoughtIxCols {
|
||||
pub id: __sdk::__query_builder::IxCol<Thought, u64>,
|
||||
pub time: __sdk::__query_builder::IxCol<Thought, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for Thought {
|
||||
type IxCols = ThoughtIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
ThoughtIxCols {
|
||||
id: __sdk::__query_builder::IxCol::new(table_name, "id"),
|
||||
time: __sdk::__query_builder::IxCol::new(table_name, "time"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for Thought {}
|
||||
@@ -1,159 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use super::thought_type::Thought;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `thoughts`.
|
||||
///
|
||||
/// Obtain a handle from the [`ThoughtsTableAccess::thoughts`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.thoughts()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.thoughts().on_insert(...)`.
|
||||
pub struct ThoughtsTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<Thought>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `thoughts`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait ThoughtsTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`ThoughtsTableHandle`], which mediates access to the table `thoughts`.
|
||||
fn thoughts(&self) -> ThoughtsTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl ThoughtsTableAccess for super::RemoteTables {
|
||||
fn thoughts(&self) -> ThoughtsTableHandle<'_> {
|
||||
ThoughtsTableHandle {
|
||||
imp: self.imp.get_table::<Thought>("thoughts"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThoughtsInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct ThoughtsDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for ThoughtsTableHandle<'ctx> {
|
||||
type Row = Thought;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = Thought> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = ThoughtsInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ThoughtsInsertCallbackId {
|
||||
ThoughtsInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: ThoughtsInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = ThoughtsDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ThoughtsDeleteCallbackId {
|
||||
ThoughtsDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: ThoughtsDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThoughtsUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for ThoughtsTableHandle<'ctx> {
|
||||
type UpdateCallbackId = ThoughtsUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> ThoughtsUpdateCallbackId {
|
||||
ThoughtsUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: ThoughtsUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `id` unique index on the table `thoughts`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`ThoughtsIdUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.thoughts().id().find(...)`.
|
||||
pub struct ThoughtsIdUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<Thought, u64>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> ThoughtsTableHandle<'ctx> {
|
||||
/// Get a handle on the `id` unique index on the table `thoughts`.
|
||||
pub fn id(&self) -> ThoughtsIdUnique<'ctx> {
|
||||
ThoughtsIdUnique {
|
||||
imp: self.imp.get_unique_constraint::<u64>("id"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ThoughtsIdUnique<'ctx> {
|
||||
/// Find the subscribed row whose `id` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &u64) -> Option<Thought> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table = client_cache.get_or_make_table::<Thought>("thoughts");
|
||||
_table.add_unique_constraint::<u64>("id", |row| &row.id);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<Thought>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse("TableUpdate<Thought>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `Thought`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait thoughtsQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `Thought`.
|
||||
fn thoughts(&self) -> __sdk::__query_builder::Table<Thought>;
|
||||
}
|
||||
|
||||
impl thoughtsQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn thoughts(&self) -> __sdk::__query_builder::Table<Thought> {
|
||||
__sdk::__query_builder::Table::new("thoughts")
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::activity_type::Activity;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub(super) struct UpdateActivityArgs {
|
||||
pub activity: Activity,
|
||||
}
|
||||
|
||||
impl From<UpdateActivityArgs> for super::Reducer {
|
||||
fn from(args: UpdateActivityArgs) -> Self {
|
||||
Self::UpdateActivity {
|
||||
activity: args.activity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for UpdateActivityArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the reducer `update_activity`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteReducers`].
|
||||
pub trait update_activity {
|
||||
/// Request that the remote module invoke the reducer `update_activity` to run as soon as possible.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`update_activity:update_activity_then`] to run a callback after the reducer completes.
|
||||
fn update_activity(&self, activity: Activity) -> __sdk::Result<()> {
|
||||
self.update_activity_then(activity, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `update_activity` to run as soon as possible,
|
||||
/// registering `callback` to run when we are notified that the reducer completed.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and its status can be observed with the `callback`.
|
||||
fn update_activity_then(
|
||||
&self,
|
||||
activity: Activity,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
impl update_activity for super::RemoteReducers {
|
||||
fn update_activity_then(
|
||||
&self,
|
||||
activity: Activity,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(UpdateActivityArgs { activity }, callback)
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::action_type::Action;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub(super) struct UpdateUpcomingArgs {
|
||||
pub upcoming: Vec<Action>,
|
||||
}
|
||||
|
||||
impl From<UpdateUpcomingArgs> for super::Reducer {
|
||||
fn from(args: UpdateUpcomingArgs) -> Self {
|
||||
Self::UpdateUpcoming {
|
||||
upcoming: args.upcoming,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for UpdateUpcomingArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the reducer `update_upcoming`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteReducers`].
|
||||
pub trait update_upcoming {
|
||||
/// Request that the remote module invoke the reducer `update_upcoming` to run as soon as possible.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`update_upcoming:update_upcoming_then`] to run a callback after the reducer completes.
|
||||
fn update_upcoming(&self, upcoming: Vec<Action>) -> __sdk::Result<()> {
|
||||
self.update_upcoming_then(upcoming, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `update_upcoming` to run as soon as possible,
|
||||
/// registering `callback` to run when we are notified that the reducer completed.
|
||||
///
|
||||
/// This method returns immediately, and errors only if we are unable to send the request.
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and its status can be observed with the `callback`.
|
||||
fn update_upcoming_then(
|
||||
&self,
|
||||
upcoming: Vec<Action>,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
impl update_upcoming for super::RemoteReducers {
|
||||
fn update_upcoming_then(
|
||||
&self,
|
||||
upcoming: Vec<Action>,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(UpdateUpcomingArgs { upcoming }, callback)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "@ppc/marts",
|
||||
"version": "1.0.0",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@flowershow/remark-wiki-link": "^3.4.0",
|
||||
"builtin-modules": "^5.0.0",
|
||||
"esbuild": "^0.27.3",
|
||||
"mdast": "^3.0.0",
|
||||
"obsidian": "^1.12.3",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "mize-spacetime"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
spacetimedb = { version = "2.4.0", features = ["unstable"] }
|
||||
log = "0.4"
|
||||
chrono = { version = "0.4.45", features = [], default-features = false }
|
||||
chrono-tz = { version = "0.10.4", default-features = false }
|
||||
url = "2.5.8"
|
||||
@@ -1,394 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::{DateTime, TimeZone};
|
||||
use chrono_tz::Europe;
|
||||
use log::*;
|
||||
use spacetimedb::SpacetimeType;
|
||||
use spacetimedb::sys::raw::index_id_from_name;
|
||||
use spacetimedb::{Identity, ReducerContext, ProcedureContext, Table, Timestamp, reducer, table, procedure};
|
||||
use spacetimedb::http::*;
|
||||
|
||||
#[table(accessor = profiles, public)]
|
||||
pub struct Profile {
|
||||
#[primary_key]
|
||||
identity: Identity,
|
||||
#[unique]
|
||||
username: String,
|
||||
}
|
||||
|
||||
#[table(accessor = task_part, public)]
|
||||
pub struct TaskPart {
|
||||
#[primary_key]
|
||||
identity: Identity,
|
||||
start_of_current: Timestamp,
|
||||
current: Action,
|
||||
upcoming: Vec<Action>,
|
||||
}
|
||||
|
||||
#[table(accessor = activities, public)]
|
||||
pub struct Activity {
|
||||
#[primary_key]
|
||||
#[auto_inc]
|
||||
id: u64,
|
||||
name: String,
|
||||
task: Option<u64>,
|
||||
parents_ids: Vec<u64>,
|
||||
}
|
||||
|
||||
#[table(accessor = tasks, public)]
|
||||
pub struct Task {
|
||||
#[primary_key]
|
||||
#[auto_inc]
|
||||
id: u64,
|
||||
done: bool,
|
||||
name: String,
|
||||
parent: Option<u64>,
|
||||
}
|
||||
|
||||
#[table(accessor = thoughts, public)]
|
||||
pub struct Thought {
|
||||
#[primary_key]
|
||||
#[auto_inc]
|
||||
id: u64,
|
||||
text: String,
|
||||
user: Identity,
|
||||
#[index(btree)]
|
||||
time: Timestamp,
|
||||
}
|
||||
|
||||
#[table(accessor = action)]
|
||||
#[derive(Clone)]
|
||||
pub struct Action {
|
||||
name: String,
|
||||
activity: Option<u64>,
|
||||
task: Option<u64>,
|
||||
start: Option<Timestamp>,
|
||||
end: Option<Timestamp>,
|
||||
duration: Option<u64>,
|
||||
}
|
||||
|
||||
impl Action {
|
||||
pub fn name_only(name: String) -> Self {
|
||||
Action {
|
||||
name,
|
||||
activity: None,
|
||||
task: None,
|
||||
start: None,
|
||||
end: None,
|
||||
duration: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[reducer]
|
||||
pub fn add_thought(ctx: &ReducerContext, thought: String) -> Result<(), String> {
|
||||
ctx.db.thoughts().try_insert(Thought {
|
||||
id: 0,
|
||||
text: thought,
|
||||
user: ctx.sender(),
|
||||
time: ctx.timestamp,
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[table(accessor = log_events, public, index(accessor = log_events_index, btree(columns = [user, end])))]
|
||||
pub struct LogEvent {
|
||||
#[primary_key]
|
||||
#[auto_inc]
|
||||
id: u64,
|
||||
start: Timestamp,
|
||||
end: Timestamp,
|
||||
action: Action,
|
||||
user: Identity,
|
||||
}
|
||||
|
||||
#[reducer(init)]
|
||||
pub fn init(_ctx: &ReducerContext) {
|
||||
// Called when the module is initially published
|
||||
info!("hiiiiiii from ppc module");
|
||||
}
|
||||
|
||||
// the one from ppc, hardcoded rn, as we for now only support config at run time.
|
||||
// this needs to be loaded at build time. (this code runs as a wasm module on the spacetimedb server)
|
||||
const PPC_AUTH_CLIENT_ID: &str = "363331597203734531";
|
||||
|
||||
#[reducer(client_connected)]
|
||||
pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
check_auth(ctx)?;
|
||||
info!("hooooo");
|
||||
|
||||
// add user
|
||||
let _ = ctx.db.profiles().try_insert(Profile {
|
||||
identity: ctx.sender(),
|
||||
username: ctx.sender().to_string(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_auth(ctx: &ReducerContext) -> Result<(), String> {
|
||||
let jwt = ctx
|
||||
.sender_auth()
|
||||
.jwt()
|
||||
.ok_or("Authentication required".to_string())?;
|
||||
if jwt.issuer() != "https://auth.ppc.social" {
|
||||
return Err("Invalid issuer".to_string());
|
||||
}
|
||||
|
||||
if !jwt.audience().iter().any(|a| a == PPC_AUTH_CLIENT_ID) {
|
||||
return Err("Invalid audience".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[reducer(client_disconnected)]
|
||||
pub fn identity_disconnected(_ctx: &ReducerContext) {
|
||||
// Called everytime a client disconnects
|
||||
}
|
||||
|
||||
#[reducer]
|
||||
pub fn add_activity(ctx: &ReducerContext, name: String) {
|
||||
ctx.db.activities().insert(Activity {
|
||||
name,
|
||||
task: None,
|
||||
id: 0, // auto-increments
|
||||
parents_ids: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
#[reducer]
|
||||
pub fn set_activity_name(ctx: &ReducerContext, id: u64, name: String) {
|
||||
if let Some(old_ac) = ctx.db.activities().id().find(id) {
|
||||
ctx.db.activities().id().update(Activity { name, ..old_ac });
|
||||
} else {
|
||||
return ();
|
||||
}
|
||||
}
|
||||
|
||||
#[reducer]
|
||||
pub fn update_upcoming(ctx: &ReducerContext, upcoming: Vec<Action>) {
|
||||
if ctx.db.task_part().identity().find(ctx.sender()).is_none() {
|
||||
ctx.db.task_part().insert(TaskPart {
|
||||
upcoming,
|
||||
identity: ctx.sender(),
|
||||
current: Action {
|
||||
name: "filler".to_string(),
|
||||
activity: None,
|
||||
task: None,
|
||||
start: None,
|
||||
end: None,
|
||||
duration: None,
|
||||
},
|
||||
start_of_current: ctx.timestamp,
|
||||
});
|
||||
} else {
|
||||
ctx.db.task_part().identity().update(TaskPart {
|
||||
upcoming,
|
||||
..ctx.db.task_part().identity().find(&ctx.sender()).unwrap()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[reducer]
|
||||
pub fn next_action(ctx: &ReducerContext, break_flag: bool, action: Option<Action>) -> Result<(), String> {
|
||||
if let Some(mut task_part) = ctx.db.task_part().identity().find(&ctx.sender()) {
|
||||
if task_part.upcoming.is_empty() {
|
||||
return Err("No upcoming actions".to_string());
|
||||
}
|
||||
let new_action = match action {
|
||||
Some(action) => action,
|
||||
// when we do "ppc nextAction -b" ... we want to do a generic break "b" action.
|
||||
None => if break_flag { Action::name_only("idk".to_string()) } else { task_part.upcoming.remove(0) },
|
||||
};
|
||||
// when break flag, push current_action again to the front of upcoming
|
||||
if break_flag {
|
||||
// insert generic "b" action when no one is specified
|
||||
task_part.upcoming.insert(0, task_part.current.clone());
|
||||
}
|
||||
let start_of_old_action = task_part.start_of_current;
|
||||
ctx.db.task_part().identity().update(TaskPart {
|
||||
upcoming: task_part.upcoming,
|
||||
identity: task_part.identity,
|
||||
current: new_action.clone(),
|
||||
start_of_current: ctx.timestamp,
|
||||
});
|
||||
|
||||
// create log event
|
||||
ctx.db.log_events().insert(LogEvent {
|
||||
start: start_of_old_action,
|
||||
end: ctx.timestamp,
|
||||
action: task_part.current,
|
||||
id: 0,
|
||||
user: ctx.sender(),
|
||||
});
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Task part data not found for this user".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[reducer]
|
||||
pub fn update_activity(ctx: &ReducerContext, activity: Activity) {
|
||||
if activity.id == 0 {
|
||||
ctx.db.activities().insert(activity);
|
||||
} else {
|
||||
ctx.db.activities().id().update(activity);
|
||||
}
|
||||
}
|
||||
|
||||
#[reducer]
|
||||
pub fn remove_activity(ctx: &ReducerContext, activity: Activity) {
|
||||
ctx.db.activities().delete(activity);
|
||||
}
|
||||
|
||||
|
||||
//////////////////// daily metric ///////////////////
|
||||
|
||||
#[table(accessor = daily_task, public)]
|
||||
pub struct DailyTask {
|
||||
id: i64,
|
||||
name: String,
|
||||
owner: Identity,
|
||||
}
|
||||
|
||||
#[table(accessor = daily_task_log, public)]
|
||||
pub struct DailyTaskLog {
|
||||
daily_task_id: i64,
|
||||
done: Timestamp,
|
||||
}
|
||||
|
||||
#[procedure]
|
||||
fn first_timetrack_date(ctx: &mut ProcedureContext, username: String) -> Result<String, String> {
|
||||
let timestamp = ctx.try_with_tx(|tx| {
|
||||
let profile = tx.db.profiles().username().find(username.clone()).ok_or("No such Profile".to_string())?;
|
||||
let event = tx.db.log_events()
|
||||
.log_events_index()
|
||||
.filter(&profile.identity)
|
||||
.nth(0)
|
||||
.ok_or("Profile has no row in log_events")?;
|
||||
let time = event.start
|
||||
.to_chrono_date_time().map_err(|e| format!("chrono error: {e}"))?
|
||||
.with_timezone(&chrono_tz::Europe::Vienna);
|
||||
Ok::<String, String>(time.format("%Y-%m-%d").to_string())
|
||||
})?;
|
||||
Ok(timestamp)
|
||||
}
|
||||
|
||||
#[procedure]
|
||||
fn export_timetrack(ctx: &mut ProcedureContext, username: String, date: String) -> Result<String, String> {
|
||||
// CEST only rn
|
||||
let start = chrono::NaiveDate::parse_from_str(&date, "%Y-%m-%d").unwrap().and_hms(5, 0, 0);
|
||||
let start_local = chrono_tz::Europe::Vienna.from_local_datetime(&start).unwrap();
|
||||
let start_utc = start_local.with_timezone(&chrono::Utc);
|
||||
let start_timestamp = Timestamp::from_micros_since_unix_epoch(start_utc.timestamp_micros());
|
||||
|
||||
let end = chrono::NaiveDate::parse_from_str(&date, "%Y-%m-%d").unwrap().and_hms(4, 0, 0) + Duration::from_hours(24);
|
||||
let end_local = chrono_tz::Europe::Vienna.from_local_datetime(&end).unwrap();
|
||||
let end_utc = end_local.with_timezone(&chrono::Utc);
|
||||
let end_timestamp = Timestamp::from_micros_since_unix_epoch(end_utc.timestamp_micros());
|
||||
|
||||
info!("start_of_day: {}", start_timestamp);
|
||||
info!("end_of_day: {}", end_timestamp);
|
||||
|
||||
let mut export = String::new();
|
||||
//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 events = ctx.with_tx(|tx|
|
||||
tx.db.log_events()
|
||||
.log_events_index()
|
||||
.filter((profile.identity, start_timestamp..end_timestamp))
|
||||
);
|
||||
for event in events {
|
||||
let start = event.start
|
||||
.to_chrono_date_time()
|
||||
.map_err(|err| err.to_string())?
|
||||
.with_timezone(&Europe::Vienna)
|
||||
.format("%H:%M");
|
||||
let end = event.end
|
||||
.to_chrono_date_time()
|
||||
.map_err(|err| err.to_string())?
|
||||
.with_timezone(&Europe::Vienna)
|
||||
.format("%H:%M");
|
||||
|
||||
let line = format!("{} to {} ... {}\n", start, end, event.action.name);
|
||||
export.push_str(line.as_str());
|
||||
}
|
||||
|
||||
return Ok(export); // don't do thoughts right now
|
||||
|
||||
export.push_str("\n");
|
||||
export.push_str("## thoughts\n");
|
||||
let thoughts = ctx.with_tx(|tx| {
|
||||
tx.db.thoughts().time().filter(start_timestamp..end_timestamp)
|
||||
});
|
||||
for thought in thoughts {
|
||||
let line = format!("- {}\n\n", thought.text);
|
||||
export.push_str(line.as_str());
|
||||
}
|
||||
|
||||
Ok(export)
|
||||
}
|
||||
|
||||
|
||||
#[procedure]
|
||||
pub fn migrate(ctx: &mut ProcedureContext) -> String {
|
||||
info!("migrate procedure called");
|
||||
//let msg = "Nothing to migrate".to_string();
|
||||
|
||||
let msg: Result<String, String> = ctx.try_with_tx(|tx| {
|
||||
let mut out = String::new();
|
||||
let mut count = 0;
|
||||
//for event in tx.db.log_events_tmp().iter() {
|
||||
//count += 1;
|
||||
//}
|
||||
out.push_str(format!("Migrated {} rows................", count).as_str());
|
||||
Ok(out)
|
||||
});
|
||||
|
||||
return match msg {
|
||||
Ok(msg) => msg,
|
||||
Err(err) => err,
|
||||
};
|
||||
}
|
||||
|
||||
#[procedure]
|
||||
pub fn test(ctx: &mut ProcedureContext) -> String {
|
||||
let mut s = String::from("hoooooooooooooooooooooo\n");
|
||||
let a: String = ctx.with_tx(|_tx| {
|
||||
let s = String::new();
|
||||
|
||||
|
||||
|
||||
|
||||
return s;
|
||||
});
|
||||
s.push_str(&format!("{}\n\n", a));
|
||||
s.push_str("\n");
|
||||
s.push_str(&format!("sender: {}", ctx.sender()));
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
#[handler]
|
||||
pub fn test_handler(ctx: &mut HandlerContext, _req: Request<Body>) -> Response<Body> {
|
||||
let res = ctx.try_with_tx(|tx| {
|
||||
let mut s = String::new();
|
||||
for a in tx.db.profiles().iter() {
|
||||
s.push_str(&format!("{} ... {}\n", a.identity, a.username));
|
||||
}
|
||||
s.push_str(&format!("sender: {}", tx.sender()));
|
||||
return Ok(s);
|
||||
});
|
||||
return Response::new(Body::from_bytes(match res {
|
||||
Ok(body) => body,
|
||||
Err(e) => e,
|
||||
}));
|
||||
}
|
||||
|
||||
#[spacetimedb::http::router]
|
||||
fn router() -> Router {
|
||||
Router::new()
|
||||
.get("/test", test_handler)
|
||||
}
|
||||
@@ -1,677 +0,0 @@
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use mize::out;
|
||||
use mize::{
|
||||
item::ItemData, mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult,
|
||||
};
|
||||
use openidconnect::{
|
||||
ClientId, DeviceAuthorizationResponse, EmptyExtraDeviceAuthorizationFields, IssuerUrl,
|
||||
OAuth2TokenResponse, RefreshToken, Scope, TokenResponse,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::{path::PathBuf, thread};
|
||||
use tracing::{info, warn};
|
||||
|
||||
// console_log macro
|
||||
// that can be copied into other files for debugging purposes
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
macro_rules! console_log {
|
||||
// Note that this is using the `log` function imported above during
|
||||
// `bare_bones`
|
||||
($($t:tt)*) => (unsafe { log(&format_args!($($t)*).to_string())})
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
use clap::Command;
|
||||
|
||||
#[derive(Default)]
|
||||
#[mize_part]
|
||||
pub struct ClientAuth {
|
||||
mize: Mize,
|
||||
secrets: Option<ClientSecrets>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ClientSecrets {
|
||||
id_token: String,
|
||||
access_token: String,
|
||||
refresh_token: String,
|
||||
expiry: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl MizePartCreate for ClientAuth {}
|
||||
|
||||
impl MizePart for ClientAuth {
|
||||
fn name(&self) -> &'static str {
|
||||
"client_auth"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-js")]
|
||||
pub fn store_secrets_js(mize: &Mize, secrets: &ClientSecrets) -> MizeResult<()> {
|
||||
use web_sys::window;
|
||||
|
||||
// Serialize struct to JSON string
|
||||
let serialized = serde_json::to_string(&secrets)?;
|
||||
|
||||
// Get browser window object
|
||||
let window = window().ok_or_else(|| mize_err!("No global window found"))?;
|
||||
|
||||
// Access local storage
|
||||
let storage = window
|
||||
.local_storage().map_err(|_| mize_err!("couldn't get local_storage"))?
|
||||
.ok_or_else(|| mize_err!("LocalStorage is not available"))?;
|
||||
|
||||
// Store the JSON string
|
||||
storage.set_item("mize_client_secrets", &serialized).map_err(|_| mize_err!("couldn't set item in local_storage"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
impl ClientAuth {
|
||||
pub fn secrets(&self) -> Option<&ClientSecrets> {
|
||||
self.secrets.as_ref()
|
||||
}
|
||||
|
||||
pub fn id_token(&self) -> MizeResult<&str> {
|
||||
if self.secrets.is_none() {
|
||||
return Err(mize_err!("Client not logged in"));
|
||||
}
|
||||
Ok(&self.secrets.as_ref().unwrap().id_token)
|
||||
}
|
||||
|
||||
pub fn access_token(&self) -> MizeResult<&str> {
|
||||
if self.secrets.is_none() {
|
||||
return Err(mize_err!("Client not logged in"));
|
||||
}
|
||||
Ok(&self.secrets.as_ref().unwrap().access_token)
|
||||
}
|
||||
|
||||
pub fn refresh_token(&self) -> MizeResult<&str> {
|
||||
if self.secrets.is_none() {
|
||||
return Err(mize_err!("Client not logged in"));
|
||||
}
|
||||
Ok(&self.secrets.as_ref().unwrap().refresh_token)
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) -> MizeResult<()> {
|
||||
#[cfg(feature = "target-js")] {
|
||||
console_log!("refresh is not implemented yet on wasm");
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")] {
|
||||
let secrets = self.secrets.clone().ok_or(mize_err!(
|
||||
"no client secrets, meaning client_auth.refresh() thread can't use the refresh token"
|
||||
))?;
|
||||
let new_secrets = self.mize.spawn_async_blocking(
|
||||
"auth.refresh_flow",
|
||||
refresh_flow(self.mize.clone(), secrets),
|
||||
)?;
|
||||
store_secrets(&mut self.mize, &new_secrets)?;
|
||||
self.secrets = Some(new_secrets);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn login(&mut self) -> MizeResult<()> {
|
||||
#[cfg(feature = "target-os")] {
|
||||
if self.secrets().is_none() {
|
||||
let secrets = self
|
||||
.mize
|
||||
.spawn_async_blocking("auth.login_flow", auth_flow(self.mize.clone()))?;
|
||||
store_secrets(&mut self.mize, &secrets)?;
|
||||
self.secrets = Some(secrets);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "target-js")] {
|
||||
if self.secrets().is_none() {
|
||||
let mut mize_clone = self.mize.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
console_log!("before auth_flow");
|
||||
let secrets = match auth_flow(mize_clone.clone()).await {
|
||||
Ok(secrets) => secrets,
|
||||
Err(e) => {
|
||||
mize_clone.report_err(e);
|
||||
return ();
|
||||
}
|
||||
};
|
||||
console_log!("after auth_flow");
|
||||
if let Err(e) = store_secrets_js(&mut mize_clone, &secrets) {
|
||||
mize_clone.report_err(e);
|
||||
return ();
|
||||
}
|
||||
let mut auth = match mize_clone.get_part_native::<ClientAuth>("client_auth") {
|
||||
Ok(auth) => auth,
|
||||
Err(e) => {
|
||||
mize_clone.report_err(e);
|
||||
return ();
|
||||
}
|
||||
};
|
||||
auth.secrets = Some(secrets);
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_refresh_thread(&mut self) -> MizeResult<()> {
|
||||
|
||||
// background thread for os targets
|
||||
#[cfg(not(feature = "target-js"))]
|
||||
{
|
||||
let mut mize_clone = self.mize.clone();
|
||||
self.mize.spawn_background("auth.refresh", move || loop {
|
||||
info!("hi from auth.refresh thread...");
|
||||
thread::sleep(Duration::from_secs(33)); // TERRIBLE hack
|
||||
let auth = mize_clone.get_part_native::<ClientAuth>("client_auth")?;
|
||||
let expiry = auth.secrets.as_ref().unwrap().expiry.clone();
|
||||
info!("auth.refresh thread: now: {}", Utc::now());
|
||||
info!("auth.refresh thread: expiry: {}", expiry);
|
||||
let to_sleep = expiry - Utc::now() - TimeDelta::minutes(20);
|
||||
/*
|
||||
if to_sleep < TimeDelta::from_std(Duration::from_secs(0))? {
|
||||
if let Err(e) = auth.refresh() {
|
||||
e.log();
|
||||
}
|
||||
}
|
||||
*/
|
||||
drop(auth);
|
||||
|
||||
info!("auth.refresh: thread going to sleep for: {}", to_sleep);
|
||||
thread::sleep(to_sleep.to_std()?);
|
||||
|
||||
info!("refresh_thread: sleeping done");
|
||||
let mut auth = mize_clone.get_part_native::<ClientAuth>("auth")?;
|
||||
auth.refresh()?;
|
||||
drop(auth);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// spawn an async background task doing the refresh
|
||||
#[cfg(feature = "target-js")] {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
// Get the window object
|
||||
let window = web_sys::window().expect("no global `window` exists");
|
||||
|
||||
// Clone mize for the closure
|
||||
let mize_clone = self.mize.clone();
|
||||
|
||||
// Create a closure that will be called periodically
|
||||
let refresh_closure = Closure::wrap(Box::new(move || {
|
||||
let mut mize_clone = mize_clone.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let mut auth = mize_clone.get_part_native::<ClientAuth>("client_auth");
|
||||
match auth {
|
||||
Ok(mut auth_part) => {
|
||||
if let Some(secrets) = auth_part.secrets() {
|
||||
let now = Utc::now();
|
||||
let expiry = secrets.expiry;
|
||||
let time_until_expiry = expiry - now;
|
||||
|
||||
// Refresh if token will expire in less than 20 minutes
|
||||
if time_until_expiry < TimeDelta::minutes(20) {
|
||||
if let Err(e) = auth_part.refresh() {
|
||||
console_log!("Error refreshing token: {:?}", e);
|
||||
} else {
|
||||
console_log!("Token refreshed successfully");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
console_log!("Error getting auth part: {:?}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}) as Box<dyn FnMut()>);
|
||||
|
||||
// Set up interval to run every 50 minutes (300000 ms)
|
||||
let interval_ms = 3000000;
|
||||
let _ = window.set_interval_with_callback_and_timeout_and_arguments_0(
|
||||
refresh_closure.as_ref().unchecked_ref(),
|
||||
interval_ms,
|
||||
);
|
||||
|
||||
// Leak the closure to prevent it from being dropped
|
||||
refresh_closure.forget();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn part_client_auth(mize: &mut Mize) -> MizeResult<()> {
|
||||
let mize_clone = mize.clone();
|
||||
|
||||
let mut auth_part = ClientAuth {
|
||||
secrets: None,
|
||||
mize: mize.clone(),
|
||||
};
|
||||
|
||||
let secrets = match load_secrets(mize) {
|
||||
Ok(secrets) => Some(secrets),
|
||||
Err(err) => {
|
||||
tracing::warn!("err reading secrets: {:?}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
auth_part.secrets = secrets.clone();
|
||||
|
||||
// do a refresh if the tokens have expired
|
||||
if secrets.is_some() && secrets.clone().unwrap().expiry < Utc::now() {
|
||||
auth_part.refresh()?;
|
||||
}
|
||||
|
||||
|
||||
// start refresh tread if configured
|
||||
if mize
|
||||
.get_config("auth.client.refresh_thread")
|
||||
.unwrap_or(ItemData::new())
|
||||
.value_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
auth_part.start_refresh_thread()?;
|
||||
}
|
||||
|
||||
|
||||
// cli command for os
|
||||
#[cfg(feature = "target-os")] {
|
||||
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
|
||||
cli.set_command("ppc");
|
||||
cli.subcommand(Command::new("login"), move |_, mut mize| {
|
||||
let mut auth = mize.get_part_native::<ClientAuth>("client_auth")?;
|
||||
let secrets = if auth.secrets.is_some() {
|
||||
match mize.spawn_async_blocking(
|
||||
"client_auth",
|
||||
refresh_flow(mize.clone(), auth.secrets.clone().unwrap()),
|
||||
) {
|
||||
Ok(secrets) => secrets,
|
||||
Err(err) => {
|
||||
out!("err refreshing secrets: {:?}", err);
|
||||
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone.clone()))?
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone.clone()))?
|
||||
};
|
||||
store_secrets(&mut mize, &secrets)?;
|
||||
auth.secrets = Some(secrets);
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
|
||||
// register command for obsidian
|
||||
#[cfg(feature = "target-obsidian")] {
|
||||
use crate::MartsCommand;
|
||||
console_log!("register obsidian command...");
|
||||
|
||||
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian")?;
|
||||
obsidian.add_command(MartsCommand {
|
||||
id: "mize-login".to_string(),
|
||||
name: "Mize Login".to_string(),
|
||||
callback: Box::new(|mut mize| {
|
||||
let mut auth = mize.get_part_native::<crate::ClientAuth>("client_auth")?;
|
||||
let res = auth.login();
|
||||
console_log!("auth.login done: {:?}", res);
|
||||
res
|
||||
}),
|
||||
})?;
|
||||
}
|
||||
|
||||
|
||||
auth_part.secrets = secrets;
|
||||
|
||||
mize.register_part(Box::new(auth_part))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_secrets(mize: &mut Mize) -> MizeResult<ClientSecrets> {
|
||||
#[cfg(feature = "target-os")] {
|
||||
let mize_dir = mize.get_config("data_dir")?.value_string()?;
|
||||
let secrets_file_path = PathBuf::from(mize_dir).join("client_secrets.json");
|
||||
|
||||
let secrets_string = std::fs::read_to_string(secrets_file_path)?;
|
||||
let secrets: ClientSecrets = serde_json::from_str(&secrets_string)?;
|
||||
return Ok(secrets);
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-js")] {
|
||||
use web_sys::window;
|
||||
|
||||
// Get browser window object
|
||||
let window = window().ok_or_else(|| mize_err!("No global window found"))?;
|
||||
|
||||
// Access local storage
|
||||
let storage = window
|
||||
.local_storage().map_err(|_| mize_err!("couldn't get local_storage"))?
|
||||
.ok_or_else(|| mize_err!("LocalStorage is not available"))?;
|
||||
|
||||
// Retrieve the string item
|
||||
return match storage.get_item("mize_client_secrets").map_err(|_| mize_err!("couldn't get item from local_storage"))? {
|
||||
Some(json_str) => {
|
||||
// Deserialize JSON string back into Client struct
|
||||
let secrets: ClientSecrets = serde_json::from_str(json_str.as_str())?;
|
||||
Ok(secrets)
|
||||
}
|
||||
None => Err(mize_err!("no secrets")), // Key does not exist
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn store_secrets(mize: &mut Mize, secrets: &ClientSecrets) -> MizeResult<()> {
|
||||
|
||||
#[cfg(feature = "target-os")] {
|
||||
let mize_dir = mize.get_config("store")?.value_string()?;
|
||||
let secrets_file_path = PathBuf::from(mize_dir.clone())
|
||||
.join("misc")
|
||||
.join("client_secrets.json");
|
||||
std::fs::create_dir_all(PathBuf::from(mize_dir).join("misc"))?;
|
||||
|
||||
let secrets_string = serde_json::to_string(secrets)?;
|
||||
log::info!("secrets_file_path: {}", secrets_file_path.display());
|
||||
//std::fs::write(secrets_file_path, secrets_string)?;
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(secrets_file_path)?
|
||||
.write_all(&secrets_string.as_bytes())?;
|
||||
|
||||
// update the spacetimedb_token in XDG_CONFIG_HOME/spacetime/cli.toml config file if configured to do so
|
||||
if mize
|
||||
.get_config("auth.client.update_spacetime_token")
|
||||
.unwrap_or(ItemData::empty())
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
update_spacetimedb_token(&secrets.id_token.as_str())?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
use openidconnect::reqwest::Client as HttpClient;
|
||||
use openidconnect::DeviceAuthorizationUrl;
|
||||
use std::time::Duration;
|
||||
//use wasm_bindgen::JsCast;
|
||||
|
||||
|
||||
async fn refresh_flow(mut mize: Mize, old_secrets: ClientSecrets) -> MizeResult<ClientSecrets> {
|
||||
out!("Refresh flow...");
|
||||
let issuer = mize.get_config("auth.client.issuer")?.value_string()?;
|
||||
let client_id = mize.get_config("auth.client.client_id")?.value_string()?;
|
||||
|
||||
let issuer = IssuerUrl::new(issuer)?;
|
||||
let client_id = ClientId::new(client_id);
|
||||
|
||||
let http_client = HttpClient::new();
|
||||
|
||||
// Teach openidconnect-rs about the device authorization url.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct MyExtraProviderMetadata {
|
||||
device_authorization_endpoint: String,
|
||||
}
|
||||
use openidconnect::core::{
|
||||
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,
|
||||
CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
|
||||
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType,
|
||||
CoreSubjectIdentifierType,
|
||||
};
|
||||
use openidconnect::AdditionalProviderMetadata;
|
||||
use openidconnect::ProviderMetadata;
|
||||
impl AdditionalProviderMetadata for MyExtraProviderMetadata {}
|
||||
type MyProviderMetadata = ProviderMetadata<
|
||||
MyExtraProviderMetadata,
|
||||
CoreAuthDisplay,
|
||||
CoreClientAuthMethod,
|
||||
CoreClaimName,
|
||||
CoreClaimType,
|
||||
CoreGrantType,
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJweKeyManagementAlgorithm,
|
||||
CoreJsonWebKey,
|
||||
CoreResponseMode,
|
||||
CoreResponseType,
|
||||
CoreSubjectIdentifierType,
|
||||
>;
|
||||
|
||||
// Discover provider metadata
|
||||
let provider_metadata: MyProviderMetadata =
|
||||
MyProviderMetadata::discover_async(issuer, &http_client).await?;
|
||||
|
||||
let device_auth_url = provider_metadata
|
||||
.additional_metadata()
|
||||
.device_authorization_endpoint
|
||||
.clone();
|
||||
|
||||
let client = CoreClient::from_provider_metadata(
|
||||
provider_metadata,
|
||||
client_id,
|
||||
None, // public client
|
||||
)
|
||||
.set_device_authorization_url(DeviceAuthorizationUrl::new(device_auth_url)?);
|
||||
|
||||
let refresh_token = RefreshToken::new(old_secrets.refresh_token);
|
||||
let res = client
|
||||
.exchange_refresh_token(&refresh_token)?
|
||||
.request_async(&http_client)
|
||||
.await?;
|
||||
let expires_in = res.expires_in().ok_or(mize_err!(
|
||||
"no expiry information was in token response of the refresh flow"
|
||||
))?;
|
||||
|
||||
return Ok(ClientSecrets {
|
||||
id_token: res.id_token().unwrap().to_string(),
|
||||
access_token: res.access_token().secret().to_owned(),
|
||||
refresh_token: res.refresh_token().unwrap().secret().to_owned(),
|
||||
expiry: Utc::now() + expires_in,
|
||||
});
|
||||
}
|
||||
|
||||
async fn auth_flow(mut mize: Mize) -> MizeResult<ClientSecrets> {
|
||||
let issuer = mize.get_config("auth.client.issuer")?.value_string()?;
|
||||
let client_id = mize.get_config("auth.client.client_id")?.value_string()?;
|
||||
|
||||
let issuer = IssuerUrl::new(issuer)?;
|
||||
let client_id = ClientId::new(client_id);
|
||||
|
||||
let http_client = HttpClient::new();
|
||||
|
||||
// Teach openidconnect-rs about the device authorization url.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct MyExtraProviderMetadata {
|
||||
device_authorization_endpoint: String,
|
||||
}
|
||||
use openidconnect::core::{
|
||||
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,
|
||||
CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
|
||||
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType,
|
||||
CoreSubjectIdentifierType,
|
||||
};
|
||||
use openidconnect::AdditionalProviderMetadata;
|
||||
use openidconnect::ProviderMetadata;
|
||||
impl AdditionalProviderMetadata for MyExtraProviderMetadata {}
|
||||
type MyProviderMetadata = ProviderMetadata<
|
||||
MyExtraProviderMetadata,
|
||||
CoreAuthDisplay,
|
||||
CoreClientAuthMethod,
|
||||
CoreClaimName,
|
||||
CoreClaimType,
|
||||
CoreGrantType,
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJweKeyManagementAlgorithm,
|
||||
CoreJsonWebKey,
|
||||
CoreResponseMode,
|
||||
CoreResponseType,
|
||||
CoreSubjectIdentifierType,
|
||||
>;
|
||||
|
||||
// Discover provider metadata
|
||||
let provider_metadata: MyProviderMetadata =
|
||||
MyProviderMetadata::discover_async(issuer, &http_client).await?;
|
||||
|
||||
let device_auth_url = provider_metadata
|
||||
.additional_metadata()
|
||||
.device_authorization_endpoint
|
||||
.clone();
|
||||
|
||||
let client = CoreClient::from_provider_metadata(
|
||||
provider_metadata,
|
||||
client_id,
|
||||
None, // public client
|
||||
)
|
||||
.set_device_authorization_url(DeviceAuthorizationUrl::new(device_auth_url)?);
|
||||
|
||||
// ===== DEVICE AUTH REQUEST =====
|
||||
let details: Result<DeviceAuthorizationResponse<EmptyExtraDeviceAuthorizationFields>, _> =
|
||||
client
|
||||
.exchange_device_code()
|
||||
.add_scope(Scope::new("openid".into()))
|
||||
.add_scope(Scope::new("profile".into()))
|
||||
.add_scope(Scope::new("offline_access".into()))
|
||||
.request_async(&http_client)
|
||||
.await;
|
||||
|
||||
let details = match details {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
out!("err: {:?}", err);
|
||||
out!("err: {}", err);
|
||||
panic!();
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "target-obsidian")] {
|
||||
console_log!("details: {:?}", details);
|
||||
use std::str::FromStr;
|
||||
|
||||
use js_sys::{Array, Function, JsString, Reflect};
|
||||
use mize::error::IntoMizeResultJsValue;
|
||||
use wasm_bindgen::convert::TryFromJsValue;
|
||||
|
||||
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian")?;
|
||||
let show_device_code = Reflect::get(&obsidian.plugin(), &JsValue::from_str("showDeviceCode")).mres()?;
|
||||
let args = Array::new();
|
||||
args.push(&JsValue::from_str(details.user_code().secret().as_str()));
|
||||
args.push(&JsValue::from_str(details.verification_uri_complete().unwrap().secret().as_str()));
|
||||
|
||||
Reflect::apply(&show_device_code.dyn_into::<Function>().mres()?, obsidian.plugin(), &args).mres()?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")] {
|
||||
out!(
|
||||
"If your browser does not open automatically, open this URL in it:\n{}\n",
|
||||
details.verification_uri_complete().unwrap().secret()
|
||||
);
|
||||
out!(
|
||||
"Or enter this PPC login code:\n{}\n",
|
||||
details.user_code().secret()
|
||||
);
|
||||
|
||||
if let Some(_) = details.verification_uri_complete() {
|
||||
let _ = webbrowser::open(details.verification_uri_complete().unwrap().secret());
|
||||
}
|
||||
}
|
||||
|
||||
let token_response = client
|
||||
.exchange_device_access_token(&details)?
|
||||
.request_async(
|
||||
&http_client,
|
||||
async |dur: Duration| {
|
||||
#[cfg(feature = "target-os")] {
|
||||
tokio::time::sleep(dur).await;
|
||||
}
|
||||
#[cfg(feature = "target-js")] {
|
||||
console_log!("sleeping");
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
let promise = js_sys::Promise::new(&mut |resolve, _| {
|
||||
web_sys::window()
|
||||
.unwrap()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, dur.as_millis() as i32)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
// Await the JS Promise like a Rust Future
|
||||
let _ = JsFuture::from(promise).await;
|
||||
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
// ===== TOKENS =====
|
||||
let id_token = token_response
|
||||
.extra_fields()
|
||||
.id_token()
|
||||
.ok_or(mize_err!("no id_token"))?;
|
||||
|
||||
let refresh = token_response
|
||||
.refresh_token()
|
||||
.ok_or(mize_err!("no refresh token present"))?;
|
||||
|
||||
let expires_in = token_response.expires_in().ok_or(mize_err!(
|
||||
"no expiry information was in token response of the refresh flow"
|
||||
))?;
|
||||
|
||||
Ok(ClientSecrets {
|
||||
id_token: id_token.to_string(),
|
||||
access_token: token_response.access_token().secret().to_string(),
|
||||
refresh_token: refresh.secret().to_owned(),
|
||||
expiry: Utc::now() + expires_in,
|
||||
})
|
||||
}
|
||||
|
||||
fn update_spacetimedb_token(new_token: &str) -> MizeResult<()> {
|
||||
// Resolve config directory
|
||||
let config_dir = match std::env::var("XDG_CONFIG_HOME") {
|
||||
Ok(val) => PathBuf::from(val),
|
||||
Err(_) => {
|
||||
let home = std::env::var("HOME")?;
|
||||
std::path::PathBuf::from(home).join(".config")
|
||||
}
|
||||
};
|
||||
|
||||
let file_path = config_dir.join("spacetime").join("cli.toml");
|
||||
|
||||
if !file_path.exists() {
|
||||
warn!("spacetime config file does not exist, not updating token there");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Read existing file (or create empty document if it doesn't exist)
|
||||
let content = std::fs::read_to_string(&file_path).unwrap_or_default();
|
||||
let mut data = ItemData::from_toml(content.as_str())?;
|
||||
|
||||
// Update the field
|
||||
data.set_path("spacetimedb_token", new_token)?;
|
||||
|
||||
// Write back
|
||||
std::fs::write(&file_path, data.to_toml()?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
pub fn main() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "my dashbaord" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,431 +0,0 @@
|
||||
import { createClient } from "@libsql/client";
|
||||
import { drizzle } from "drizzle-orm/libsql";
|
||||
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
||||
import { eq } from "drizzle-orm";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
export async function init(ppc: PPC) {
|
||||
ppc.c2vi = new C2vi(ppc);
|
||||
|
||||
ppc.cli
|
||||
.command("bed", "command to run when c2vi goes to bed")
|
||||
.action(async () => {
|
||||
await save_habitica_dailies(ppc);
|
||||
await save_habitica_logs(ppc);
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("clearTodos", "command to run when c2vi goes to bed")
|
||||
.alias("clt")
|
||||
.action(async () => {
|
||||
const todos = await ppc.habitica.get_tasks();
|
||||
for (const todo of todos) {
|
||||
await ppc.habitica.delete_task(todo.id);
|
||||
}
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("listTodos", "command to run when c2vi goes to bed")
|
||||
.alias("dut")
|
||||
.action(async () => {
|
||||
const todos = await ppc.habitica.get_tasks();
|
||||
for (const todo of todos) {
|
||||
console.log(todo.text);
|
||||
}
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("buyHealthPotion", "command to run when c2vi goes to bed")
|
||||
.alias("buyh")
|
||||
.action(async () => {
|
||||
await ppc.habitica.api_request("POST", "/user/buy-health-potion");
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("addActionItems", "command to run when c2vi goes to bed")
|
||||
.alias("aal")
|
||||
.action(async () => {
|
||||
await add_action_items(ppc);
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("test", "a command to run some test code")
|
||||
.alias("t")
|
||||
.action(async () => {});
|
||||
|
||||
ppc.cli
|
||||
.command("skip [num]", "command to skip a habitica todo")
|
||||
.alias("sk")
|
||||
.action((num = 1) => {
|
||||
skip_habitica_todo(ppc, parseInt(num));
|
||||
});
|
||||
|
||||
ppc.cli.command("dumpLog", "dump the complete habitica log").action(() => {
|
||||
dump_habitica_logs(ppc);
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("listDailies", "list all dailies")
|
||||
.action(async (folder, options) => {
|
||||
const allDailies = await ppc.c2vi.db.select().from(dailiesTable).all();
|
||||
for (const task of allDailies) {
|
||||
console.log(task.id + ": " + task.text);
|
||||
}
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("printTask <id>", "print a specific todo")
|
||||
.action(async (id) => {
|
||||
const data = await ppc.habitica.api_request("GET", `/tasks/${id}`, {});
|
||||
console.log(data.history);
|
||||
});
|
||||
}
|
||||
|
||||
class C2vi {
|
||||
[key: string]: any;
|
||||
|
||||
constructor(ppc: PPC) {
|
||||
this.ppc = ppc;
|
||||
this.db = drizzle(
|
||||
createClient({
|
||||
url: "file://" + ppc.config.local_storage_path + "/data.db",
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(d) {
|
||||
const pad = (n) => n.toString().padStart(2, "0");
|
||||
|
||||
// Custom YYYY-MM-DD format
|
||||
const formatted = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||
return formatted;
|
||||
}
|
||||
|
||||
// ============= SCHEMA DEFINITION =============
|
||||
const dailiesTable = sqliteTable("habitica_dailies", {
|
||||
id: text("id").primaryKey(),
|
||||
text: text("text").notNull(),
|
||||
notes: text("notes"),
|
||||
priority: text("priority"), // 0.1, 1, 1.5, 2
|
||||
frequency: text("frequency"), // 'daily', 'weekly', etc.
|
||||
});
|
||||
const habiticaLogTable = sqliteTable("habitica_log", {
|
||||
date: text("date").primaryKey(),
|
||||
data: text("data").notNull(),
|
||||
});
|
||||
|
||||
async function save_habitica_dailies(ppc) {
|
||||
console.log("######### cli bed cmd #########");
|
||||
await ppc.c2vi.db.run(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_dailies (id TEXT PRIMARY KEY, text TEXT NOT NULL, notes TEXT, priority TEXT, frequency TEXT)",
|
||||
);
|
||||
await ppc.c2vi.db.run(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_log (date TEXT PRIMARY KEY, data TEXT NOT NULL)",
|
||||
);
|
||||
|
||||
// ============= DATABASE INITIALIZATION =============
|
||||
if (!ppc.config.habitica.user_id || !ppc.config.habitica.api_token) {
|
||||
console.error(
|
||||
"Error: HABITICA_USER_ID and HABITICA_API_TOKEN env vars required",
|
||||
);
|
||||
console.error(
|
||||
"Get these from Habitica Settings > API and configure them in the ppc.config",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const dailies = await ppc.habitica.get_tasks("dailys");
|
||||
|
||||
await writeDailiesToDatabase(dailies, ppc);
|
||||
}
|
||||
|
||||
async function dump_habitica_logs(ppc) {
|
||||
const entries = await ppc.c2vi.db.select().from(habiticaLogTable).all();
|
||||
for (const entry of entries) {
|
||||
console.log(entry.date + ": ", JSON.parse(entry.data));
|
||||
}
|
||||
}
|
||||
|
||||
async function skip_habitica_todo(ppc, num) {
|
||||
await ppc.c2vi.db.run(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_dailies (id TEXT PRIMARY KEY, text TEXT NOT NULL, notes TEXT, priority TEXT, frequency TEXT)",
|
||||
);
|
||||
await ppc.c2vi.db.run(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_log (date TEXT PRIMARY KEY, data TEXT NOT NULL)",
|
||||
);
|
||||
|
||||
const todos = await ppc.habitica.get_tasks("todos");
|
||||
|
||||
const date = new Date();
|
||||
const save_for_yesterday = date.getHours() < 12;
|
||||
if (save_for_yesterday) {
|
||||
console.log(
|
||||
"Saving logs for yesterday!!!! because it is " +
|
||||
date.getHours() +
|
||||
"hours of the day",
|
||||
);
|
||||
date.setDate(date.getDate() - 1);
|
||||
}
|
||||
|
||||
const result = await ppc.c2vi.db
|
||||
.select()
|
||||
.from(habiticaLogTable)
|
||||
.where(eq(habiticaLogTable.date, formatDate(date)));
|
||||
|
||||
const data = result[0]
|
||||
? JSON.parse(result[0].data)
|
||||
: {
|
||||
dailies_done: [],
|
||||
dailies_skipped: [],
|
||||
todos_done: [],
|
||||
todos_skipped: [],
|
||||
};
|
||||
|
||||
for (let i = 0; i < num; i++) {
|
||||
const todo = todos[i];
|
||||
if (!todo) continue;
|
||||
await ppc.habitica.delete_task(todo.id);
|
||||
await incrementHabit(ppc);
|
||||
|
||||
data.todos_skipped.push({
|
||||
id: todo.id,
|
||||
text: todo.text,
|
||||
notes: todo.notes || "",
|
||||
checklist: todo.checklist || [],
|
||||
tags: todo.tags || [],
|
||||
});
|
||||
}
|
||||
|
||||
// saving to db
|
||||
await ppc.c2vi.db
|
||||
.insert(habiticaLogTable)
|
||||
.values({ date: formatDate(date), data: JSON.stringify(data) })
|
||||
.onConflictDoUpdate({
|
||||
target: habiticaLogTable.date,
|
||||
set: {
|
||||
data: JSON.stringify(data),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function save_habitica_logs(ppc) {
|
||||
const date = new Date();
|
||||
const save_for_yesterday = date.getHours() < 12;
|
||||
if (save_for_yesterday) {
|
||||
console.log(
|
||||
"Saving logs for yesterday!!!! because it is " +
|
||||
date.getHours() +
|
||||
"hours of the day",
|
||||
);
|
||||
date.setDate(date.getDate() - 1);
|
||||
}
|
||||
|
||||
const result = await ppc.c2vi.db
|
||||
.select()
|
||||
.from(habiticaLogTable)
|
||||
.where(eq(habiticaLogTable.date, formatDate(date)));
|
||||
|
||||
const data = result[0]
|
||||
? JSON.parse(result[0].data)
|
||||
: {
|
||||
dailies_done: [],
|
||||
dailies_skipped: [],
|
||||
todos_done: [],
|
||||
todos_skipped: [],
|
||||
};
|
||||
|
||||
// dailies
|
||||
const dailies = await ppc.habitica.get_tasks("dailys");
|
||||
for (const daily of dailies) {
|
||||
let completed = daily.completed;
|
||||
let isDue = daily.isDue;
|
||||
if (save_for_yesterday) {
|
||||
const yesterday = daily.history.reduce((prev, current) => {
|
||||
return prev.date > current.date ? prev : current;
|
||||
});
|
||||
completed = yesterday.completed;
|
||||
isDue = yesterday.isDue;
|
||||
}
|
||||
if (!isDue) {
|
||||
continue;
|
||||
}
|
||||
if (completed) {
|
||||
data.dailies_done.push(daily.id);
|
||||
console.log("daily-done: " + daily.text);
|
||||
} else {
|
||||
data.dailies_skipped.push(daily.id);
|
||||
console.log("daily-skipped: " + daily.text);
|
||||
}
|
||||
}
|
||||
|
||||
// todos done
|
||||
const todos = await ppc.habitica.get_tasks("completedTodos");
|
||||
for (const todo of todos) {
|
||||
const dateCompleted = new Date(todo.dateCompleted);
|
||||
|
||||
// check if this todo is already saved in yesterday's log entry
|
||||
if (save_for_yesterday) {
|
||||
// get yesterday log entry
|
||||
const result = await ppc.c2vi.db
|
||||
.select()
|
||||
.from(habiticaLogTable)
|
||||
.where(eq(habiticaLogTable.date, formatDate(date)));
|
||||
|
||||
const data = result[0]
|
||||
? JSON.parse(result[0].data)
|
||||
: {
|
||||
dailies_done: [],
|
||||
dailies_skipped: [],
|
||||
todos_done: [],
|
||||
todos_skipped: [],
|
||||
};
|
||||
|
||||
// check if this todo is already saved in yesterday's log entry
|
||||
const todoIndex = data.todos_done.findIndex((t) => t.id === todo.id);
|
||||
if (todoIndex !== -1) {
|
||||
continue; // skipp saving this todo
|
||||
}
|
||||
}
|
||||
|
||||
if (dateCompleted.toDateString() == date.toDateString()) {
|
||||
console.log("todo-done:", todo.text);
|
||||
data.todos_done.push({
|
||||
id: todo.id,
|
||||
text: todo.text,
|
||||
notes: todo.notes || "",
|
||||
checklist: todo.checklist || [],
|
||||
tags: todo.tags || [],
|
||||
});
|
||||
}
|
||||
|
||||
// we save_for_yesterday but the todo was completed today... also save that for this day
|
||||
if (
|
||||
save_for_yesterday &&
|
||||
dateCompleted.toDateString() == new Date().toDateString()
|
||||
) {
|
||||
console.log("todo-done:", todo.text);
|
||||
data.todos_done.push({
|
||||
id: todo.id,
|
||||
text: todo.text,
|
||||
notes: todo.notes || "",
|
||||
checklist: todo.checklist || [],
|
||||
tags: todo.tags || [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// saving to db
|
||||
await ppc.c2vi.db
|
||||
.insert(habiticaLogTable)
|
||||
.values({ date: formatDate(date), data: JSON.stringify(data) })
|
||||
.onConflictDoUpdate({
|
||||
target: habiticaLogTable.date,
|
||||
set: {
|
||||
data: JSON.stringify(data),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function writeDailiesToDatabase(dailies, ppc) {
|
||||
console.log(`Syncing ${dailies.length} dailies to database...`);
|
||||
|
||||
const records = dailies.map((daily) => ({
|
||||
id: daily.id,
|
||||
text: daily.text,
|
||||
notes: daily.notes || "",
|
||||
priority: daily.priority?.toString() || null,
|
||||
frequency: daily.frequency || "daily",
|
||||
}));
|
||||
|
||||
async function upsertMany(records) {
|
||||
for (const record of records) {
|
||||
await ppc.c2vi.db
|
||||
.insert(dailiesTable)
|
||||
.values(record)
|
||||
.onConflictDoUpdate({
|
||||
target: dailiesTable.id,
|
||||
set: {
|
||||
text: record.text,
|
||||
notes: record.notes,
|
||||
priority: record.priority,
|
||||
frequency: record.frequency,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await upsertMany(records);
|
||||
}
|
||||
|
||||
async function incrementHabit(ppc) {
|
||||
const habits = await ppc.habitica.get_tasks("habits");
|
||||
let id = "";
|
||||
for (const habit of habits) {
|
||||
if ((habit.text = "skipped a planned task")) {
|
||||
id = habit.id;
|
||||
}
|
||||
}
|
||||
await ppc.habitica.api_request("POST", `tasks/${id}/score/down`);
|
||||
}
|
||||
|
||||
async function add_action_items(ppc: PPC) {
|
||||
let task_ids = [];
|
||||
|
||||
const tmpFile = path.join(os.tmpdir(), `habitica-tasks-${Date.now()}.md`);
|
||||
const initialContent = `
|
||||
gu 10min TIMER
|
||||
uw 15min FROG
|
||||
b coffee/tee/kakau
|
||||
uw FROG
|
||||
b breakfast, brush teeth, lüften
|
||||
uw FROG
|
||||
|
||||
lunch
|
||||
|
||||
uw 15min TIMER of next days FROG
|
||||
fw
|
||||
dt 30min TIMER
|
||||
|
||||
fun 20:00 (smth that can be stopped easily)
|
||||
se 21:40
|
||||
read
|
||||
bed 22:10
|
||||
`;
|
||||
|
||||
fs.writeFileSync(tmpFile, initialContent.trim());
|
||||
|
||||
spawnSync("nvim", [tmpFile], { stdio: "inherit" });
|
||||
|
||||
// read file
|
||||
const listStr = fs.readFileSync(tmpFile, "utf8");
|
||||
|
||||
// delete file
|
||||
fs.unlinkSync(tmpFile);
|
||||
|
||||
// Simple parser: filter out empty lines
|
||||
const tasks = listStr
|
||||
.split("\n")
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t.length > 0);
|
||||
|
||||
// 5. Add tasks to Habitica
|
||||
for (const todoText of tasks) {
|
||||
const data = await ppc.habitica.api_request(
|
||||
"POST",
|
||||
"tasks/user",
|
||||
{},
|
||||
{ text: todoText, type: "todo" },
|
||||
);
|
||||
task_ids.push(data.id);
|
||||
console.log(`Added to-do: '${todoText}' with id '${data.id}'`);
|
||||
}
|
||||
|
||||
// 6. Move tasks to bottom
|
||||
for (const taskId of task_ids) {
|
||||
await ppc.habitica.api_request("POST", `tasks/${taskId}/move/to/-1`);
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
export function c2vi_obsidian_canvas_patch(mize: any) {
|
||||
const plugin = mize.get_part_native("obsidian");
|
||||
|
||||
window.pc = patchCanvas;
|
||||
window.upc = () => {
|
||||
unpatchCanvas(plugin, plugin.app.workspace.activeLeaf);
|
||||
};
|
||||
|
||||
console.log("hereeeeeeeeeeeeeeeee");
|
||||
// canvas patching
|
||||
plugin.registerEvent(
|
||||
plugin.app.workspace.on("file-open", (file: any) => {
|
||||
if (file?.extension === "canvas") {
|
||||
maybePathCanvas(plugin, plugin.app.workspace.activeLeaf);
|
||||
}
|
||||
}),
|
||||
);
|
||||
plugin.registerEvent(
|
||||
plugin.app.workspace.on("active-leaf-change", (file: any) => {
|
||||
if (file?.extension === "canvas") {
|
||||
maybePathCanvas(plugin, plugin.app.workspace.activeLeaf);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const maybePathCanvas = (plugin: PPCObsidianPlugin, leaf: any | null) => {
|
||||
console.log("maybePathCanvas called");
|
||||
const file = plugin.app.workspace.getActiveFile();
|
||||
|
||||
if (file?.extension !== "canvas") return;
|
||||
console.log("patching canvas.....");
|
||||
|
||||
const canvas = plugin.app.workspace.activeLeaf.view.canvas;
|
||||
if ("isPatched" in canvas) return;
|
||||
|
||||
patchCanvas(plugin, leaf, canvas);
|
||||
};
|
||||
|
||||
function patchCanvas(plugin: PPCObsidianPlugin, leaf: any, canvas: any) {
|
||||
canvas.isPatched = true;
|
||||
const canvasEl = canvas.canvasEl;
|
||||
const canvasWrapperEl = canvasEl.parentElement;
|
||||
const overlay = createDiv();
|
||||
overlay.setAttr("class", "c2vi-canvas-overlay");
|
||||
const canvas_card_menue = canvasWrapperEl.childNodes[1];
|
||||
canvasWrapperEl.insertBefore(overlay, canvas_card_menue);
|
||||
Object.assign(overlay.style, {
|
||||
position: "absolute",
|
||||
inset: "0",
|
||||
//zIndex: "1337",
|
||||
});
|
||||
overlay.addEventListener("contextmenu", (e) => {
|
||||
if (e.button === 2) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
overlay.addEventListener("mouseout", (e) => {
|
||||
const menu = document.getElementsByClassName("menu")[0];
|
||||
if (!menu) return;
|
||||
menu.addEventListener("pointerup", (e) => {
|
||||
console.log("pointerup on menu", e);
|
||||
e.target.click();
|
||||
/*
|
||||
menu.dispatchEvent(
|
||||
new MouseEvent("mousedown", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
button: e.button,
|
||||
buttons: e.buttons,
|
||||
view: window,
|
||||
}),
|
||||
);
|
||||
*/
|
||||
});
|
||||
});
|
||||
|
||||
setupEventPropagation("pointerdown", PointerEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("pointerup", PointerEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("mousedown", MouseEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("mouseup", MouseEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("click", MouseEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("dragstart", DragEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("dragstop", DragEvent, overlay, canvasWrapperEl);
|
||||
|
||||
window.a = canvasWrapperEl;
|
||||
window.b = overlay;
|
||||
|
||||
const mouseEvents = [
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"click",
|
||||
"dblclick",
|
||||
//"mousemove",
|
||||
"mouseover",
|
||||
"mouseout",
|
||||
"wheel",
|
||||
];
|
||||
|
||||
mouseEvents.forEach((eventType) => {
|
||||
canvasWrapperEl.addEventListener(
|
||||
eventType,
|
||||
(event) => {
|
||||
console.log(`Mouse Event Triggered: ${event.type}`, event);
|
||||
},
|
||||
{ passive: true },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function unpatchCanvas(plugin: PPCObsidianPlugin, leaf: any) {
|
||||
const canvas = plugin.app.workspace.activeLeaf.view.canvas;
|
||||
if (!canvas.isPatched) return;
|
||||
canvas.isPatched = false;
|
||||
const canvasEl = canvas.canvasEl;
|
||||
const canvasWrapperEl = canvasEl.parentElement;
|
||||
|
||||
const targets = canvasEl.querySelectorAll(":scope > .c2vi-canvas-overlay");
|
||||
|
||||
targets.forEach((child) => child.remove());
|
||||
}
|
||||
|
||||
function setupEventPropagation(type: string, evClass, overlay: any, target) {
|
||||
overlay.addEventListener(type, (originalEvent) => {
|
||||
originalEvent.stopPropagation();
|
||||
originalEvent.stopImmediatePropagation();
|
||||
originalEvent.preventDefault();
|
||||
const newEvent = new evClass(type, originalEvent);
|
||||
if (type == "pointerdown" && originalEvent.ctrlKey == true) {
|
||||
dispatchContextMenue(
|
||||
target,
|
||||
originalEvent.clientX,
|
||||
originalEvent.clientY,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (type === "pointerdown") {
|
||||
window.downEv = newEvent;
|
||||
}
|
||||
if (type === "pointerup") {
|
||||
window.upEv = newEvent;
|
||||
}
|
||||
newEvent.isTrusted = true;
|
||||
console.log("original Event of", type, originalEvent);
|
||||
console.log("new Event of", type, newEvent);
|
||||
target.dispatchEvent(newEvent);
|
||||
});
|
||||
}
|
||||
|
||||
function dispatchContextMenue(target, x, y) {
|
||||
const newEvent = new PointerEvent("contextmenu", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
view: window,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
pointerType: "mouse",
|
||||
button: 2,
|
||||
buttons: 2,
|
||||
pointerId: 1,
|
||||
isPrimary: true,
|
||||
});
|
||||
target.dispatchEvent(newEvent);
|
||||
}
|
||||
@@ -1,995 +0,0 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use chrono::{Timelike, Utc};
|
||||
use clap::{Arg, Command};
|
||||
#[cfg(feature = "desktop")]
|
||||
use mize::item::ItemData;
|
||||
use rusqlite::{params, Connection};
|
||||
use serde_json::{json, Value};
|
||||
use clap::ArgMatches;
|
||||
use cmd_lib::run_cmd as sh;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult};
|
||||
use tracing::instrument::WithSubscriber;
|
||||
|
||||
use crate::cli::CliPart;
|
||||
use crate::habitica::Habitica;
|
||||
use crate::SpacetimePart;
|
||||
use generated::spacetime::{
|
||||
Action, ActivitiesTableAccess, Activity, DbConnection, LogEventsTableAccess, TaskPart, TaskPartTableAccess, activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess, update_activity, update_upcoming
|
||||
};
|
||||
use spacetimedb_sdk::{
|
||||
credentials, DbContext, Error, Event, Identity, Status,
|
||||
TableWithPrimaryKey,
|
||||
};
|
||||
|
||||
static MIZE_SRC: &str = "/home/me/work/config/mize";
|
||||
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 --exclude /src --exclude obsidian-brat --exclude forked-zed";
|
||||
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
pub struct C2vi {
|
||||
mize: Mize,
|
||||
db: Mutex<Option<Connection>>,
|
||||
}
|
||||
|
||||
impl MizePart for C2vi {
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
mize.new_opt("c2vi.local_storage_path");
|
||||
}
|
||||
}
|
||||
impl MizePartCreate for C2vi {}
|
||||
|
||||
impl C2vi {
|
||||
fn with_db<F, R>(&self, f: F) -> MizeResult<R>
|
||||
where
|
||||
F: FnOnce(&Connection) -> MizeResult<R>,
|
||||
{
|
||||
let guard = self
|
||||
.db
|
||||
.lock()
|
||||
.map_err(|e| mize_err!("DB lock poisoned: {}", e))?;
|
||||
let conn = guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| mize_err!("DB not initialized"))?;
|
||||
f(conn)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn part_c2vi(mize: &mut Mize) -> MizeResult<()> {
|
||||
c2vi_deploy_things(mize)?;
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
if mize
|
||||
.get_config("c2vi.dash.enable")
|
||||
.unwrap_or(ItemData::empty())
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
launch_c2vi_dashboard(mize);
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-obsidian")] {
|
||||
use crate::MartsCommand;
|
||||
|
||||
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian").unwrap();
|
||||
obsidian.add_command(MartsCommand {
|
||||
id: "sc2-publish-daily-journal".to_string(),
|
||||
name: "SC2 Publish Daily Journal".to_string(),
|
||||
callback: Box::new(|mut mize| {
|
||||
let matrix = mize.get_part_native::<crate::matrix::MatrixPart>("matrix")?;
|
||||
//matrix.send_message(room_id);
|
||||
|
||||
Ok(())
|
||||
}),
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_deploy(mize: Mize, matches: &ArgMatches, hostname: &str) -> MizeResult<()> {
|
||||
let path = match hostname {
|
||||
"mac" => "/home/me/here/mize",
|
||||
_ => "/home/me/here/mize",
|
||||
};
|
||||
let is_release = match hostname {
|
||||
"mac" => true,
|
||||
_ => false,
|
||||
};
|
||||
let ld_lib_path = std::env::var("LD_LIBRARY_PATH")?;
|
||||
if is_release {
|
||||
sh!(echo cmd: cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize --release)?;
|
||||
sh!(cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize --release)?;
|
||||
sh!(echo cmd: patchelf --set-rpath $ld_lib_path $SRC/mize/target/release/mize)?;
|
||||
let _ = sh!(patchelf --set-rpath $ld_lib_path $SRC/mize/target/release/mize);
|
||||
sh!(echo cmd: scp $SRC/mize/target/release/mize $hostname:$path)?;
|
||||
sh!(scp $SRC/mize/target/release/mize $hostname:$path)?;
|
||||
} else {
|
||||
sh!(echo cmd: cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize)?;
|
||||
sh!(cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize)?;
|
||||
sh!(echo cmd: patchelf --set-rpath $ld_lib_path $SRC/mize/target/debug/mize)?;
|
||||
let _ = sh!(patchelf --set-rpath $ld_lib_path $SRC/mize/target/debug/mize);
|
||||
sh!(echo cmd: scp $SRC/mize/target/debug/mize $hostname:$path)?;
|
||||
sh!(scp $SRC/mize/target/debug/mize $hostname:$path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn c2vi_deploy_things(mize: &mut Mize) -> MizeResult<()> {
|
||||
let mut cli = mize.get_part_native::<CliPart>("cli")?;
|
||||
cli.set_command("dev");
|
||||
|
||||
let bin_arg = Arg::new("bin").short('b');
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("deploy-lush").alias("dlu").arg(&bin_arg),
|
||||
Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "te")),
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("deploy-mac").alias("dmc").arg(&bin_arg),
|
||||
Box::new(move |matches: &ArgMatches, mize| new_deploy(mize, matches, "mac")),
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("deploy-files").alias("dfi").arg(&bin_arg),
|
||||
Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "files")),
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("deploy-ppc").alias("dpp").arg(&bin_arg),
|
||||
Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "ppc-hosting")),
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("deploy-nico").alias("dni").arg(&bin_arg),
|
||||
Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "nico")),
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("deploy-main").alias("dma").arg(&bin_arg),
|
||||
Box::new(main_deploy),
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("deploy-obsidian").alias("do").arg(&bin_arg),
|
||||
Box::new(obsidian_deploy),
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("deploy-spacetime").alias("dsp").arg(&bin_arg),
|
||||
Box::new(deploy_spacetime),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
pub fn launch_c2vi_dashboard(mize: &mut Mize) {
|
||||
std::env::remove_var("WAYLAND_DISPLAY");
|
||||
std::env::set_var("DISPLAY", ":0");
|
||||
use dioxus::desktop::{muda::Menu, Config, WindowBuilder};
|
||||
let mize_clone = mize.clone();
|
||||
LaunchBuilder::new()
|
||||
.with_context_provider(move || Box::new(mize_clone.clone()))
|
||||
.with_cfg(desktop! {
|
||||
Config::new().with_window(WindowBuilder::new().with_decorations(false).with_title("SCI display"))
|
||||
})
|
||||
.launch(c2vi_dash_main);
|
||||
}
|
||||
|
||||
//use spacetimedb_sdk::spacetime_module::QueryBuilder;
|
||||
|
||||
/*
|
||||
pub fn use_spacetime<T: Fn(QueryBuilder)>(query: T) {
|
||||
let mut mize = use_context::<Mize>();
|
||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap();
|
||||
let con = spacetime.con();
|
||||
con.subscription_builder()
|
||||
.on_applied(move |ctx| {
|
||||
let data = ctx.db.task_part().identity().find(&ctx.identity()).unwrap();
|
||||
current.set(data.current.name.clone());
|
||||
})
|
||||
.add_query(query)
|
||||
.subscribe();
|
||||
con.db
|
||||
.task_part()
|
||||
.on_update(move |_, old: &TaskPart, new: &TaskPart| {
|
||||
current.set(new.current.name.clone());
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
//pub fn use_by_pk(identity: Identity)
|
||||
//pub fn use_one<T>
|
||||
|
||||
|
||||
pub fn c2vi_dash_main() -> Element {
|
||||
let mut current = use_signal_sync(|| "".to_string());
|
||||
let mut next = use_signal_sync(|| "".to_string());
|
||||
//TaskPart::
|
||||
//use_one()
|
||||
use_effect(move || {
|
||||
let mut mize = use_context::<Mize>();
|
||||
let spacetime = match mize.get_part_native::<SpacetimePart>("spacetime") {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
e.log();
|
||||
return;
|
||||
},
|
||||
};
|
||||
let con = spacetime.con();
|
||||
con.subscription_builder()
|
||||
.on_applied(move |ctx| {
|
||||
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());
|
||||
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())))
|
||||
.subscribe();
|
||||
con.db
|
||||
.task_part()
|
||||
.on_update(move |_, _old: &TaskPart, new: &TaskPart| {
|
||||
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! {
|
||||
div {
|
||||
style: "text-align: center; padding: 2rem; font-size: 120px;",
|
||||
p {
|
||||
"{current}"
|
||||
}
|
||||
p {
|
||||
style: "font-size: 50px; color: grey; opacity: 0.7;",
|
||||
"{next}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn c2vi_client(mize: &mut Mize) -> MizeResult<()> {
|
||||
//let storage_path = mize.get_config("c2vi.local_storage_path")?.as_
|
||||
let storage_path = "/home/me/work/app-data/ppc";
|
||||
//println!("storage_path: {}", storage_path);
|
||||
let db_path = std::path::PathBuf::from(&storage_path).join("data.db");
|
||||
let conn = Connection::open(&db_path)
|
||||
.map_err(|e| mize_err!("Failed to open C2vi database at {:?}: {}", db_path, e))?;
|
||||
|
||||
setup_tables(&conn)?;
|
||||
|
||||
let c2vi_part = C2vi {
|
||||
mize: mize.clone(),
|
||||
db: Mutex::new(Some(conn)),
|
||||
};
|
||||
mize.register_part(Box::new(c2vi_part))?;
|
||||
|
||||
// Register CLI subcommands
|
||||
let mut cli = mize.get_part_native::<CliPart>("cli")?;
|
||||
register_subcommands(&mut cli);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_tables(conn: &Connection) -> MizeResult<()> {
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_dailies (
|
||||
id TEXT PRIMARY KEY,
|
||||
text TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
priority TEXT,
|
||||
frequency TEXT
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| mize_err!("DB error creating habitica_dailies: {}", e))?;
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_log (
|
||||
date TEXT PRIMARY KEY,
|
||||
data TEXT NOT NULL
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| mize_err!("DB error creating habitica_log: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_subcommands(cli: &mut CliPart) {
|
||||
cli.subcommand(
|
||||
Command::new("c2vi-dash").about("Run the c2vi dashboard gui"),
|
||||
|_matches, mut _mize| Ok(dioxus::launch(super::dashboard::main)),
|
||||
);
|
||||
cli.subcommand(
|
||||
Command::new("bed").about("Command to run when c2vi goes to bed"),
|
||||
|_matches, mut mize| {
|
||||
save_habitica_dailies(&mut mize)?;
|
||||
save_habitica_logs(&mut mize)?;
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("clearTodos")
|
||||
.about("Clear all Habitica todos")
|
||||
.alias("clt"),
|
||||
|_matches, mut mize| {
|
||||
let todos = {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
hab.get_tasks("todos")?
|
||||
};
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
if let Some(arr) = todos.as_array() {
|
||||
for todo in arr {
|
||||
if let Some(id) = todo["id"].as_str() {
|
||||
hab.delete_task(id)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("listTodos")
|
||||
.about("List all Habitica todos")
|
||||
.alias("dut"),
|
||||
|_matches, mut mize| {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
let todos = hab.get_tasks("todos")?;
|
||||
if let Some(arr) = todos.as_array() {
|
||||
for todo in arr {
|
||||
if let Some(text) = todo["text"].as_str() {
|
||||
println!("{}", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("buyHealthPotion")
|
||||
.about("Buy a health potion")
|
||||
.alias("buyh"),
|
||||
|_matches, mut mize| {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
hab.api_request(
|
||||
reqwest::Method::POST,
|
||||
"user/buy-health-potion".to_string(),
|
||||
json!({}),
|
||||
)?;
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("addActionItems")
|
||||
.about("Add action items from temp file")
|
||||
.alias("aal"),
|
||||
|_matches, mut mize| add_action_items(&mut mize),
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("skip")
|
||||
.about("Skip a Habitica todo")
|
||||
.alias("sk")
|
||||
.arg(Arg::new("num").default_value("1")),
|
||||
|matches, mut mize| {
|
||||
let num: usize = matches
|
||||
.get_one::<String>("num")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap_or(1);
|
||||
skip_habitica_todo(&mut mize, num)
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("dumpLog").about("Dump the complete Habitica log"),
|
||||
|_matches, mut mize| dump_habitica_logs(&mut mize),
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("listDailies").about("List all dailies from local DB"),
|
||||
|_matches, mut mize| {
|
||||
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
|
||||
c2vi.with_db(|conn| {
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT id, text FROM habitica_dailies")
|
||||
.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
let rows = stmt
|
||||
.query_map([], |row| {
|
||||
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
||||
})
|
||||
.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
for row in rows {
|
||||
let (id, text) = row.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
println!("{}: {}", id, text);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("printTask")
|
||||
.about("Print a specific task from Habitica")
|
||||
.arg(Arg::new("id").required(true)),
|
||||
|matches, mut mize| {
|
||||
let id = matches.get_one::<String>("id").unwrap();
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
let data = hab.api_request(reqwest::Method::GET, format!("tasks/{}", id), json!({}))?;
|
||||
if let Some(history) = data.get("history") {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(history).unwrap_or_default()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&data).unwrap_or_default()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// ============= Business Logic =============
|
||||
|
||||
fn format_date(dt: chrono::DateTime<Utc>) -> String {
|
||||
dt.format("%Y-%m-%d").to_string()
|
||||
}
|
||||
|
||||
/// Get the target date, adjusted for "save for yesterday" logic (before noon = yesterday)
|
||||
fn get_target_date() -> (String, bool) {
|
||||
let now = Utc::now();
|
||||
let save_for_yesterday = now.hour() < 12;
|
||||
let date = if save_for_yesterday {
|
||||
now - chrono::Duration::days(1)
|
||||
} else {
|
||||
now
|
||||
};
|
||||
if save_for_yesterday {
|
||||
println!(
|
||||
"Saving logs for yesterday because it is {} hours of the day",
|
||||
now.hour()
|
||||
);
|
||||
}
|
||||
(format_date(date), save_for_yesterday)
|
||||
}
|
||||
|
||||
/// Load existing log entry from DB, or return a default empty one
|
||||
fn get_or_create_log(conn: &Connection, date: &str) -> MizeResult<Value> {
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT data FROM habitica_log WHERE date = ?1")
|
||||
.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
let result: Option<String> = stmt.query_row(params![date], |row| row.get(0)).ok();
|
||||
match result {
|
||||
Some(data_str) => {
|
||||
serde_json::from_str(&data_str).map_err(|e| mize_err!("JSON parse error: {}", e))
|
||||
}
|
||||
None => Ok(json!({
|
||||
"dailies_done": [],
|
||||
"dailies_skipped": [],
|
||||
"todos_done": [],
|
||||
"todos_skipped": []
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// Upsert a log entry into the database
|
||||
fn upsert_log(conn: &Connection, date: &str, data: &Value) -> MizeResult<()> {
|
||||
let data_str =
|
||||
serde_json::to_string(data).map_err(|e| mize_err!("JSON serialize error: {}", e))?;
|
||||
conn.execute(
|
||||
"INSERT INTO habitica_log (date, data) VALUES (?1, ?2)
|
||||
ON CONFLICT(date) DO UPDATE SET data=excluded.data",
|
||||
params![date, data_str],
|
||||
)
|
||||
.map_err(|e| mize_err!("DB error upserting log: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save_habitica_dailies(mize: &mut Mize) -> MizeResult<()> {
|
||||
println!("######### cli bed cmd #########");
|
||||
|
||||
// Fetch dailies from Habitica
|
||||
let dailies = {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
hab.get_tasks("dailys")?
|
||||
};
|
||||
|
||||
// Write to database
|
||||
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
|
||||
c2vi.with_db(|conn| {
|
||||
if let Some(arr) = dailies.as_array() {
|
||||
println!("Syncing {} dailies to database...", arr.len());
|
||||
for daily in arr {
|
||||
let id = daily["id"].as_str().unwrap_or("");
|
||||
let text = daily["text"].as_str().unwrap_or("");
|
||||
let notes = daily["notes"].as_str().unwrap_or("");
|
||||
let priority = daily["priority"].to_string();
|
||||
let frequency = daily["frequency"].as_str().unwrap_or("daily");
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO habitica_dailies (id, text, notes, priority, frequency)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
text=excluded.text,
|
||||
notes=excluded.notes,
|
||||
priority=excluded.priority,
|
||||
frequency=excluded.frequency",
|
||||
params![id, text, notes, priority, frequency],
|
||||
)
|
||||
.map_err(|e| mize_err!("DB error writing daily: {}", e))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn save_habitica_logs(mize: &mut Mize) -> MizeResult<()> {
|
||||
let (date_str, save_for_yesterday) = get_target_date();
|
||||
|
||||
// Fetch dailies and completed todos from Habitica
|
||||
let (dailies, completed_todos) = {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
let dailies = hab.get_tasks("dailys")?;
|
||||
let completed_todos = hab.get_tasks("completedTodos")?;
|
||||
(dailies, completed_todos)
|
||||
};
|
||||
|
||||
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
|
||||
c2vi.with_db(|conn| {
|
||||
let mut data = get_or_create_log(conn, &date_str)?;
|
||||
|
||||
// Process dailies
|
||||
if let Some(arr) = dailies.as_array() {
|
||||
for daily in arr {
|
||||
let is_due = if save_for_yesterday {
|
||||
// Look at history for yesterday's status
|
||||
daily["history"]
|
||||
.as_array()
|
||||
.and_then(|h| h.iter().max_by_key(|e| e["date"].as_i64().unwrap_or(0)))
|
||||
.map(|e| e["isDue"].as_bool().unwrap_or(false))
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
daily["isDue"].as_bool().unwrap_or(false)
|
||||
};
|
||||
|
||||
if !is_due {
|
||||
continue;
|
||||
}
|
||||
|
||||
let completed = if save_for_yesterday {
|
||||
daily["history"]
|
||||
.as_array()
|
||||
.and_then(|h| h.iter().max_by_key(|e| e["date"].as_i64().unwrap_or(0)))
|
||||
.map(|e| e["completed"].as_bool().unwrap_or(false))
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
daily["completed"].as_bool().unwrap_or(false)
|
||||
};
|
||||
|
||||
let daily_id = daily["id"].as_str().unwrap_or("");
|
||||
let daily_text = daily["text"].as_str().unwrap_or("");
|
||||
|
||||
if completed {
|
||||
data["dailies_done"]
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.push(json!(daily_id));
|
||||
println!("daily-done: {}", daily_text);
|
||||
} else {
|
||||
data["dailies_skipped"]
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.push(json!(daily_id));
|
||||
println!("daily-skipped: {}", daily_text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process completed todos
|
||||
if let Some(arr) = completed_todos.as_array() {
|
||||
let today_str = format_date(Utc::now());
|
||||
for todo in arr {
|
||||
let date_completed_str = todo["dateCompleted"].as_str().unwrap_or("");
|
||||
|
||||
// Parse the completion date (ISO 8601 format from Habitica)
|
||||
let date_completed = chrono::DateTime::parse_from_rfc3339(date_completed_str)
|
||||
.map(|d| format_date(d.with_timezone(&Utc)))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Check if already in the log for this date
|
||||
if save_for_yesterday {
|
||||
let existing_todos = data["todos_done"].as_array().unwrap();
|
||||
let todo_id = todo["id"].as_str().unwrap_or("");
|
||||
if existing_todos
|
||||
.iter()
|
||||
.any(|t| t["id"].as_str() == Some(todo_id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let todo_entry = json!({
|
||||
"id": todo["id"],
|
||||
"text": todo["text"],
|
||||
"notes": todo.get("notes").unwrap_or(&json!("")),
|
||||
"checklist": todo.get("checklist").unwrap_or(&json!([])),
|
||||
"tags": todo.get("tags").unwrap_or(&json!([])),
|
||||
});
|
||||
|
||||
// Save if completed on the target date
|
||||
if date_completed == date_str {
|
||||
println!("todo-done: {}", todo["text"].as_str().unwrap_or(""));
|
||||
data["todos_done"]
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.push(todo_entry.clone());
|
||||
}
|
||||
|
||||
// If saving for yesterday but todo was completed today, also record it
|
||||
if save_for_yesterday && date_completed == today_str {
|
||||
println!("todo-done: {}", todo["text"].as_str().unwrap_or(""));
|
||||
data["todos_done"].as_array_mut().unwrap().push(todo_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
upsert_log(conn, &date_str, &data)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn skip_habitica_todo(mize: &mut Mize, num: usize) -> MizeResult<()> {
|
||||
let (date_str, _) = get_target_date();
|
||||
|
||||
// Fetch todos from Habitica
|
||||
let todos = {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
hab.get_tasks("todos")?
|
||||
};
|
||||
|
||||
let arr = todos
|
||||
.as_array()
|
||||
.ok_or_else(|| mize_err!("Expected array of todos"))?;
|
||||
|
||||
// Delete and log each skipped todo
|
||||
for i in 0..num {
|
||||
let todo = match arr.get(i) {
|
||||
Some(t) => t,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let todo_id = todo["id"].as_str().unwrap_or("");
|
||||
|
||||
// Delete the todo and score the skip habit
|
||||
{
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
hab.delete_task(todo_id)?;
|
||||
}
|
||||
increment_habit(mize)?;
|
||||
|
||||
// Record in log
|
||||
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
|
||||
c2vi.with_db(|conn| {
|
||||
let mut data = get_or_create_log(conn, &date_str)?;
|
||||
data["todos_skipped"].as_array_mut().unwrap().push(json!({
|
||||
"id": todo["id"],
|
||||
"text": todo["text"],
|
||||
"notes": todo.get("notes").unwrap_or(&json!("")),
|
||||
"checklist": todo.get("checklist").unwrap_or(&json!([])),
|
||||
"tags": todo.get("tags").unwrap_or(&json!([])),
|
||||
}));
|
||||
upsert_log(conn, &date_str, &data)?;
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn increment_habit(mize: &mut Mize) -> MizeResult<()> {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
let habits = hab.get_tasks("habits")?;
|
||||
if let Some(arr) = habits.as_array() {
|
||||
for habit in arr {
|
||||
if habit["text"].as_str() == Some("skipped a planned task") {
|
||||
let id = habit["id"].as_str().unwrap_or("");
|
||||
hab.api_request(
|
||||
reqwest::Method::POST,
|
||||
format!("tasks/{}/score/down", id),
|
||||
json!({}),
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_action_items(mize: &mut Mize) -> MizeResult<()> {
|
||||
let initial_content = "\
|
||||
gu 10min TIMER
|
||||
uw 15min FROG
|
||||
b coffee/tee/kakau
|
||||
uw FROG
|
||||
b breakfast, brush teeth, lüften
|
||||
uw FROG
|
||||
|
||||
lunch
|
||||
|
||||
uw 15min TIMER of next days FROG
|
||||
fw
|
||||
dt 30min TIMER
|
||||
|
||||
fun 20:00 (smth that can be stopped easily)
|
||||
se 21:40
|
||||
read
|
||||
bed 22:10";
|
||||
|
||||
let tmp_file =
|
||||
std::env::temp_dir().join(format!("habitica-tasks-{}.md", Utc::now().timestamp()));
|
||||
std::fs::write(&tmp_file, initial_content)
|
||||
.map_err(|e| mize_err!("Failed to write temp file: {}", e))?;
|
||||
|
||||
// Open in nvim for editing
|
||||
std::process::Command::new("nvim")
|
||||
.arg(&tmp_file)
|
||||
.status()
|
||||
.map_err(|e| mize_err!("Failed to run nvim: {}", e))?;
|
||||
|
||||
// Read back and parse
|
||||
let list_str = std::fs::read_to_string(&tmp_file)
|
||||
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
|
||||
let _ = std::fs::remove_file(&tmp_file);
|
||||
|
||||
let tasks: Vec<&str> = list_str
|
||||
.lines()
|
||||
.map(|l| l.trim())
|
||||
.filter(|l| !l.is_empty())
|
||||
.collect();
|
||||
|
||||
// Add tasks to Habitica
|
||||
let mut task_ids = Vec::new();
|
||||
{
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
for todo_text in &tasks {
|
||||
let data = hab.api_request(
|
||||
reqwest::Method::POST,
|
||||
"tasks/user".to_string(),
|
||||
json!({ "text": todo_text, "type": "todo" }),
|
||||
)?;
|
||||
let id = data["id"].as_str().unwrap_or("").to_string();
|
||||
println!("Added to-do: '{}' with id '{}'", todo_text, id);
|
||||
task_ids.push(id);
|
||||
}
|
||||
|
||||
// Move all tasks to bottom
|
||||
for task_id in &task_ids {
|
||||
hab.api_request(
|
||||
reqwest::Method::POST,
|
||||
format!("tasks/{}/move/to/-1", task_id),
|
||||
json!({}),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dump_habitica_logs(mize: &mut Mize) -> MizeResult<()> {
|
||||
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
|
||||
c2vi.with_db(|conn| {
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT date, data FROM habitica_log ORDER BY date")
|
||||
.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
let rows = stmt
|
||||
.query_map([], |row| {
|
||||
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
||||
})
|
||||
.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
for row in rows {
|
||||
let (date, data_str) = row.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
let data: Value =
|
||||
serde_json::from_str(&data_str).unwrap_or(json!({"parse_error": data_str}));
|
||||
println!(
|
||||
"{}: {}",
|
||||
date,
|
||||
serde_json::to_string_pretty(&data).unwrap_or_default()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remote_deploy(matches: &ArgMatches, _: Mize, hostname: &str) -> MizeResult<()> {
|
||||
unsafe {
|
||||
/*
|
||||
std::env::set_var(
|
||||
"PATH",
|
||||
"/home/me/work/path-extra/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/home/me/.local/state/nix/profile/bin:/home/me/.nix-profile/bin",
|
||||
);
|
||||
*/
|
||||
std::env::set_var("SRC", SRC);
|
||||
std::env::set_var("HOSTNAME", hostname);
|
||||
}
|
||||
|
||||
let mut do_mize_restart = false;
|
||||
|
||||
let bin = match matches.get_one::<String>("bin") {
|
||||
Some(val) => val.to_owned(),
|
||||
None => {
|
||||
"mize".to_owned()
|
||||
}
|
||||
};
|
||||
|
||||
let remote_src_dir = match hostname {
|
||||
"lush" => "/home/me/here/config",
|
||||
"mac" => "/data/external/config",
|
||||
"files" => "/home/files/here/config",
|
||||
"nico" => "/root/work/config-c2vi",
|
||||
"ppc-hosting" => "/root/host/c2vi-config",
|
||||
_ => "/home/me/here/config",
|
||||
};
|
||||
let mize_store_dir = match hostname {
|
||||
"lush" => "/home/me/host/mize",
|
||||
"mac" => "/home/me/host/mize",
|
||||
"files" => "/home/files/storage/files/mize",
|
||||
"nico" => "/root/work/app-data/mize",
|
||||
"ppc-hosting" => "/root/host/mize",
|
||||
_ => "/home/me/host/mize",
|
||||
};
|
||||
let rclone_flag = match hostname {
|
||||
"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)?;
|
||||
|
||||
let ssh_cmd = format!(
|
||||
r#"
|
||||
if ! tmux has-session -t mize-deploy 2>/dev/null; then
|
||||
echo "Created new tmux session...."
|
||||
tmux new-session -d -s mize-deploy "nix develop git+file://{remote_src_dir}/mize#buildShell --max-jobs 1"
|
||||
else
|
||||
echo "using existing session..."
|
||||
fi
|
||||
|
||||
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}/mize/Cargo.toml --bin {bin}" 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 "MIZE_CONFIG_FILES={mize_store_dir}/config.toml" >> {mize_store_dir}/daemon.env' Enter
|
||||
tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && [[ "$HOSTNAME" == "nico" ]] && echo "PATH=/root/work/path-extra:$PATH" >> {mize_store_dir}/daemon.env' Enter
|
||||
tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && [[ "$DO_MIZE_RESTART" == "true" ]] && systemctl --user restart mize' Enter
|
||||
"#
|
||||
);
|
||||
|
||||
sh!(ssh $hostname $ssh_cmd)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn deploy_spacetime(_: &ArgMatches, _: Mize) -> MizeResult<()> {
|
||||
unsafe {
|
||||
/*
|
||||
std::env::set_var(
|
||||
"PATH",
|
||||
"/home/me/work/path-extra/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/home/me/.local/state/nix/profile/bin:/home/me/.nix-profile/bin",
|
||||
);
|
||||
*/
|
||||
std::env::set_var("SRC", SRC);
|
||||
}
|
||||
|
||||
let cmd = format!(
|
||||
r#"
|
||||
if ! tmux has-session -t mize-deploy 2>/dev/null; then
|
||||
echo "Created new tmux session...."
|
||||
systemd-run --user --scope --description="mize deploy tmux session" tmux new-session -d -s mize-deploy "nix develop git+file://$SRC/mize --impure"
|
||||
else
|
||||
echo "using existing session..."
|
||||
tmux send-keys -t mize-deploy "echo hoooooooooooooo" Enter
|
||||
fi
|
||||
|
||||
tmux send-keys -t mize-deploy "spacetime publish -p $SRC/mize/marts/spacetime/ --yes mize" Enter
|
||||
"#
|
||||
);
|
||||
sh!(/bin/sh -c "$cmd")?;
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
pub fn obsidian_deploy(_: &ArgMatches, _: Mize) -> MizeResult<()> {
|
||||
unsafe {
|
||||
std::env::set_var(
|
||||
"PATH",
|
||||
"/home/me/work/path-extra/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/home/me/.local/state/nix/profile/bin:/home/me/.nix-profile/bin",
|
||||
);
|
||||
std::env::set_var("SRC", SRC);
|
||||
}
|
||||
let cmd = format!(
|
||||
r#"
|
||||
if ! tmux has-session -t mize-deploy 2>/dev/null; then
|
||||
echo "Created new tmux session...."
|
||||
systemd-run --user --scope --description="mize deploy tmux session" tmux new-session -d -s mize-deploy "nix develop git+file://$SRC/mize --impure"
|
||||
else
|
||||
echo "using existing session..."
|
||||
fi
|
||||
tmux send-keys -t mize-deploy "deno run --config {SRC}/mize/apps/obsidian-plugin/deno.json build" Enter
|
||||
tmux send-keys -t mize-deploy "cp -r {SRC}/mize/apps/obsidian-plugin/dist/* ~/work/things/.obsidian/plugins/mize/" Enter
|
||||
"#
|
||||
);
|
||||
sh!(/bin/sh -c "$cmd")?;
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
pub fn main_deploy(_: &ArgMatches, _: Mize) -> MizeResult<()> {
|
||||
unsafe {
|
||||
std::env::set_var(
|
||||
"PATH",
|
||||
"/home/me/work/path-extra/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/home/me/.local/state/nix/profile/bin:/home/me/.nix-profile/bin",
|
||||
);
|
||||
std::env::set_var("SRC", SRC);
|
||||
}
|
||||
let cmd =
|
||||
r#"
|
||||
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"
|
||||
else
|
||||
echo "using existing session..."
|
||||
fi
|
||||
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 "systemctl --user restart mize" Enter
|
||||
"#;
|
||||
sh!(/bin/sh -c "$cmd")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn nico_notes_sync_thread(mize: Mize) -> MizeResult<()> {
|
||||
tracing::info!("starting nico_notes_sync_thread");
|
||||
loop {
|
||||
let res = sh!(/bin/bash -c r#"
|
||||
cd /root/work/nico/notes/c2vi
|
||||
git add .
|
||||
git commit -m "nico notes sync"
|
||||
git pull --rebase
|
||||
git push
|
||||
|
||||
cd /root/work/nico/notes/nexus
|
||||
git add .
|
||||
git commit -m "nico nexus notes sync"
|
||||
git pull --rebase
|
||||
git push
|
||||
|
||||
cd /root/work/nico/notes/ppc-wiki
|
||||
git add .
|
||||
git commit -m "nico ppc-wiki notes sync"
|
||||
git pull --rebase
|
||||
git push
|
||||
"#);
|
||||
if let Err(e) = res {
|
||||
tracing::error!("error in nico_notes_sync_thread: {}", e);
|
||||
};
|
||||
std::thread::sleep(std::time::Duration::from_mins(2));
|
||||
}
|
||||
}
|
||||
@@ -1,357 +0,0 @@
|
||||
use clap::{ArgMatches, Command};
|
||||
use dioxus::core::SpawnIfAsync;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
|
||||
use mize::{
|
||||
item::ItemData, mize_err, mize_part, proto::MizeMessage, Mize, MizeError, MizePart,
|
||||
MizePartCreate, MizeResult,
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
pub static MIZE_CLI_SPAWN: RefCell<Option<CliSpawn>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
struct CliSpawn {
|
||||
wait_rx: flume::Receiver<()>,
|
||||
conn_id: Option<u64>,
|
||||
mize: Mize,
|
||||
}
|
||||
|
||||
pub fn cli_spawn_setup(mize: Mize, conn_id: u64, wait_rx: flume::Receiver<()>) -> MizeResult<()> {
|
||||
MIZE_CLI_SPAWN.with(|cell| {
|
||||
*cell.borrow_mut() = Some(CliSpawn {
|
||||
wait_rx,
|
||||
conn_id: Some(conn_id),
|
||||
mize,
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cli_spawn(mize: &mut Mize, cmd: String) -> MizeResult<()> {
|
||||
// spawn locally
|
||||
if !mize.get_config("cli.daemonMode").unwrap_or(ItemData::new()).as_bool().unwrap_or(false) {
|
||||
std::process::Command::new("/bin/sh")
|
||||
.arg("-c")
|
||||
.arg(&cmd)
|
||||
.status()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// for spawning in the client if we are running in the daemon (which is eg attached to the users tty)
|
||||
MIZE_CLI_SPAWN.with(|cell| {
|
||||
if let Some(spawn) = cell.borrow().as_ref() {
|
||||
let conn_id = spawn.conn_id.ok_or(mize_err!("no conn_id set"))?;
|
||||
let conn = spawn.mize.get_connection(conn_id)?;
|
||||
conn.send(MizeMessage::new_custom("cli.spawn", cmd, conn_id))?;
|
||||
|
||||
log::info!("before wait_rx.recv()");
|
||||
spawn.wait_rx.recv()?;
|
||||
log::info!("after wait_rx.recv()");
|
||||
Ok(())
|
||||
} else {
|
||||
Err(mize_err!("no cli spawn thread_local available"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[mize_part("cli")]
|
||||
#[derive(Default)]
|
||||
pub struct CliPart {
|
||||
mize: Mize,
|
||||
cmds: HashMap<String, Command>,
|
||||
current_cmd_name: String,
|
||||
actions:
|
||||
HashMap<String, Box<dyn Fn(&ArgMatches, Mize) -> MizeResult<()> + Send + Sync + 'static>>,
|
||||
parsers: Option<Vec<Box<dyn Fn(Mize, Vec<String>) -> MizeResult<()> + Send + Sync + 'static>>>,
|
||||
}
|
||||
|
||||
pub fn cli(mize: &mut Mize) -> MizeResult<()> {
|
||||
let command = Command::new("marts-cli").allow_external_subcommands(true);
|
||||
let (spawn_wait_tx, spawn_wait_rx) = flume::unbounded::<()>();
|
||||
let mut cli_part = CliPart {
|
||||
mize: mize.clone(),
|
||||
cmds: HashMap::new(),
|
||||
current_cmd_name: "marts-cli".to_string(),
|
||||
actions: HashMap::new(),
|
||||
parsers: Some(Vec::new()),
|
||||
};
|
||||
cli_part.cmds.insert("marts-cli".to_string(), command);
|
||||
|
||||
mize.add_listener(
|
||||
"cli",
|
||||
Box::new(move |msg, mize| cli_msg_handler(msg, mize, spawn_wait_rx.clone())),
|
||||
)?;
|
||||
mize.add_listener(
|
||||
"cli.spawn_done",
|
||||
Box::new(move |_msg, _mize| {
|
||||
log::info!("before send");
|
||||
spawn_wait_tx.send(());
|
||||
log::info!("after send");
|
||||
Ok(())
|
||||
}),
|
||||
)?;
|
||||
mize.add_part(Box::new(cli_part))
|
||||
}
|
||||
|
||||
impl MizePartCreate for CliPart {}
|
||||
|
||||
impl MizePart for CliPart {
|
||||
fn init(&mut self, _mize: &mut Mize) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
if mize
|
||||
.get_config("cli.daemonMode")
|
||||
.unwrap_or(ItemData::new())
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let matches = self
|
||||
.cmds
|
||||
.remove(&self.current_cmd_name)
|
||||
.unwrap()
|
||||
.get_matches();
|
||||
let sub_cmd = match matches.subcommand_name() {
|
||||
Some(sub_cmd) => sub_cmd,
|
||||
None => {
|
||||
log::info!("No subcommand provided");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let action = match self.actions.remove(sub_cmd) {
|
||||
Some(action) => action,
|
||||
None => {
|
||||
// external parsers
|
||||
for parser in self.parsers.take().unwrap() {
|
||||
parser(
|
||||
mize.clone(),
|
||||
matches
|
||||
.subcommand_matches(sub_cmd)
|
||||
.unwrap()
|
||||
.get_many::<String>("")
|
||||
.unwrap()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
action(matches.subcommand_matches(sub_cmd).unwrap(), mize.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
mize.new_opt("cli.name");
|
||||
}
|
||||
}
|
||||
|
||||
fn cli_msg_handler(
|
||||
mut msg: MizeMessage,
|
||||
mize: &mut Mize,
|
||||
spawn_wait_rx: flume::Receiver<()>,
|
||||
) -> MizeResult<()> {
|
||||
let tmp = msg.data()?;
|
||||
let data = tmp
|
||||
.cbor()
|
||||
.as_array()
|
||||
.ok_or(mize_err!("cli message data was not an array"))?;
|
||||
let full_cmd = data
|
||||
.iter()
|
||||
.nth(0)
|
||||
.cloned()
|
||||
.ok_or(mize_err!("cli message data was empty"))?;
|
||||
let full_cmd_text = full_cmd
|
||||
.as_text()
|
||||
.ok_or(mize_err!("cli cmd was not text"))?;
|
||||
let cmd = full_cmd_text
|
||||
.split("/")
|
||||
.last()
|
||||
.ok_or(mize_err!("failed to get cmd"))?;
|
||||
let mut cli = mize.get_part_native::<CliPart>("cli").unwrap();
|
||||
let command = cli
|
||||
.cmds
|
||||
.get(cmd)
|
||||
.ok_or(mize_err!("no command {cmd} in cli part"))?;
|
||||
let matches = command
|
||||
.clone()
|
||||
.get_matches_from(data.iter().map(|v| v.as_text().unwrap()));
|
||||
let sub_cmd_name = match matches.subcommand_name() {
|
||||
Some(sub_cmd) => sub_cmd,
|
||||
None => {
|
||||
cli.send_stderr(&msg, "No subcommand provided".to_string())?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let action = match cli.actions.get_mut(sub_cmd_name) {
|
||||
Some(action) => action,
|
||||
None => {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let original_msg = msg.clone();
|
||||
let mize_clone = mize.clone();
|
||||
mize::MIZE_STDOUT.with(move |stdout| {
|
||||
*stdout.borrow_mut() = Some(Box::new(move |string| {
|
||||
let conn_id = original_msg.conn_id;
|
||||
let msg = MizeMessage::new_custom("cli.stdout", string, conn_id);
|
||||
let conn = mize_clone.get_connection(conn_id)?;
|
||||
conn.send(msg)?;
|
||||
Ok(())
|
||||
}))
|
||||
});
|
||||
|
||||
cli_spawn_setup(mize.clone(), msg.conn_id, spawn_wait_rx)?;
|
||||
|
||||
action(
|
||||
matches.subcommand_matches(sub_cmd_name).unwrap(),
|
||||
mize.clone(),
|
||||
)?;
|
||||
cli.send_done(&msg)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl CliPart {
|
||||
pub fn set_command(&mut self, cmd: &str) {
|
||||
self.current_cmd_name = cmd.to_owned();
|
||||
if !self.cmds.contains_key(cmd) {
|
||||
self.cmds
|
||||
.insert(cmd.to_owned(), Command::new(cmd.to_owned()));
|
||||
}
|
||||
}
|
||||
pub fn send_stderr(&mut self, original_msg: &MizeMessage, msg: String) -> MizeResult<()> {
|
||||
let conn_id = original_msg.conn_id;
|
||||
let msg = MizeMessage::new_custom("cli.stderr", msg, conn_id);
|
||||
let conn = self.mize.get_connection(conn_id)?;
|
||||
conn.send(msg)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn send_stdout(&mut self, original_msg: &MizeMessage, msg: String) -> MizeResult<()> {
|
||||
let conn_id = original_msg.conn_id;
|
||||
let msg = MizeMessage::new_custom("cli.stdout", msg, conn_id);
|
||||
let conn = self.mize.get_connection(conn_id)?;
|
||||
conn.send(msg)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn send_spawn(&mut self, original_msg: &MizeMessage, msg: String) -> MizeResult<()> {
|
||||
let conn_id = original_msg.conn_id;
|
||||
let msg = MizeMessage::new_custom("cli.spawn", msg, conn_id);
|
||||
let conn = self.mize.get_connection(conn_id)?;
|
||||
conn.send(msg)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn send_done(&mut self, original_msg: &MizeMessage) -> MizeResult<()> {
|
||||
let conn_id = original_msg.conn_id;
|
||||
let msg = MizeMessage::new_custom("cli.done", ItemData::new(), conn_id);
|
||||
let conn = self.mize.get_connection(conn_id)?;
|
||||
conn.send(msg)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn subcommand<T: Fn(&ArgMatches, Mize) -> MizeResult<()> + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
subcmd: Command,
|
||||
action: T,
|
||||
) -> MizeResult<()> {
|
||||
let name = subcmd.get_name().to_string();
|
||||
let cmd = self
|
||||
.cmds
|
||||
.remove(&self.current_cmd_name)
|
||||
.unwrap()
|
||||
.subcommand(subcmd);
|
||||
self.cmds.insert(self.current_cmd_name.clone(), cmd);
|
||||
self.actions.insert(name, Box::new(action));
|
||||
Ok(())
|
||||
}
|
||||
pub fn add_sub_parser<T: Fn(Mize, Vec<String>) -> MizeResult<()> + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
parser: T,
|
||||
) -> MizeResult<()> {
|
||||
self.parsers.as_mut().unwrap().push(Box::new(parser));
|
||||
Ok(())
|
||||
}
|
||||
pub fn with_cmd(
|
||||
&mut self,
|
||||
func: fn(mize: Mize, cmd: Command) -> MizeResult<Command>,
|
||||
) -> MizeResult<()> {
|
||||
let cmd = self.cmds.remove(&self.current_cmd_name).ok_or(mize_err!(
|
||||
"current command {} not found",
|
||||
self.current_cmd_name
|
||||
))?;
|
||||
let cmd = func(self.mize.clone(), cmd)?;
|
||||
self.cmds.insert(self.current_cmd_name.clone(), cmd);
|
||||
Ok(())
|
||||
}
|
||||
pub fn run(&mut self, cmdline: Vec<String>) -> MizeResult<()> {
|
||||
let cmd_name = cmdline.iter().nth(0).ok_or(mize_err!("empty cmdline"))?;
|
||||
let cmd = self.cmds.remove(cmd_name).ok_or(mize_err!("cmd {} not found in CliPart", cmd_name))?;
|
||||
|
||||
let matches = cmd.get_matches_from(cmdline);
|
||||
let sub_name = matches.subcommand_name().ok_or(mize_err!("subcommand not found"))?;
|
||||
let sub_matches = matches.subcommand_matches(sub_name).ok_or(mize_err!("subcommand matches not found"))?;
|
||||
let action = self.actions.remove(sub_name).ok_or(mize_err!("action for subcommand not found"))?;
|
||||
action(sub_matches, self.mize.clone())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// impl smth like this to run cli commands in the daemon.
|
||||
/*
|
||||
pub fn mize_thin_cli_client(bin_name: Option<&str>) -> MizeResult<()> {
|
||||
use std::io::Write;
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
let store_path =
|
||||
std::env::var("MIZE_STORE").map_err(|_| mize_err!("MIZE_STORE not set in environment"))?;
|
||||
let sock_path = store_path + "/sock";
|
||||
println!("sock_path: {}", sock_path);
|
||||
let mut cmd_line = std::env::args().collect::<Vec<_>>();
|
||||
if let Some(bin_name) = bin_name {
|
||||
cmd_line[0] = bin_name.to_owned();
|
||||
}
|
||||
let msg = MizeMessage::new_custom("cli", cmd_line, 0);
|
||||
|
||||
let mut stream = UnixStream::connect(sock_path)?;
|
||||
|
||||
stream.write_all(&msg.bytes())?;
|
||||
|
||||
// Optional: Ensure all buffered data is pushed to the socket
|
||||
stream.flush()?;
|
||||
let mut reader = BufReader::new(stream.try_clone()?);
|
||||
|
||||
loop {
|
||||
let msg: Value = ciborium::from_reader(&mut reader)?;
|
||||
let mut msg = MizeMessage::new(msg, 0);
|
||||
match msg.cmd()? {
|
||||
MessageCmd::Custom(kind) => {
|
||||
if kind == "cli.done".into_item_data() {
|
||||
break;
|
||||
}
|
||||
if kind == "cli.stdout".into_item_data() {
|
||||
println!("{}", msg.data()?.value_string()?);
|
||||
}
|
||||
if kind == "cli.stderr".into_item_data() {
|
||||
eprintln!("{}", msg.data()?.value_string()?);
|
||||
}
|
||||
if kind == "cli.spawn".into_item_data() {
|
||||
let cmd = msg.data()?.value_string()?;
|
||||
std::process::Command::new("/bin/sh")
|
||||
.arg("-c")
|
||||
.arg(&cmd)
|
||||
.status()?;
|
||||
let done_msg = MizeMessage::new_custom("cli.spawn_done", ItemData::new(), 0);
|
||||
stream.write_all(&done_msg.bytes())?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
*/
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
use mize::{Mize, MizeResult, MizeError, mize_err};
|
||||
|
||||
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)?;
|
||||
pub fn part_dev(mize: &mut Mize) -> MizeResult<()> {
|
||||
let mut cli = mize.get_part_native::<CliPart>("cli")?;
|
||||
cli.set_command("dev");
|
||||
|
||||
|
||||
|
||||
|
||||
mize.add_name_only_part("dev");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
pub use mize::*;
|
||||
|
||||
// ========== WASM-specific imports (Obsidian plugin) ==========
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
use js_sys;
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
use web_sys;
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
use crate::obsidian::{ObsidianPart, MartsCommand};
|
||||
|
||||
// ========== Native-specific imports (OS daemon) ==========
|
||||
#[cfg(feature = "target-os")]
|
||||
use serenity::{
|
||||
async_trait as serenity_async_trait,
|
||||
client::{Client, Context, EventHandler},
|
||||
model::{
|
||||
channel::Message,
|
||||
gateway::Ready,
|
||||
id::ChannelId,
|
||||
},
|
||||
prelude::GatewayIntents,
|
||||
};
|
||||
#[cfg(feature = "target-os")]
|
||||
use mize::async_trait;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[mize_part]
|
||||
pub struct DiscordPart {
|
||||
pub mize: Mize,
|
||||
// Native: serenity client
|
||||
#[cfg(feature = "target-os")]
|
||||
pub client: Option<Client>,
|
||||
}
|
||||
|
||||
impl DiscordPart {
|
||||
// ========== WASM (Obsidian plugin) methods ==========
|
||||
|
||||
/// Read a file from the Obsidian vault using the Obsidian API
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
pub fn read_obsidian_file(&self, path: &str) -> MizeResult<String> {
|
||||
// TODO: Implement using js_sys to call Obsidian's vault.read() or similar
|
||||
// Example approach:
|
||||
use wasm_bindgen::JsCast;
|
||||
use js_sys::Reflect;
|
||||
let obsidian_part = self.mize.get_part_native::<ObsidianPart>("obsidian")?;
|
||||
let plugin = &obsidian_part.plugin();
|
||||
let app = Reflect::get(plugin, &"app".into())?;
|
||||
let vault = Reflect::get(&app, &"vault".into())?;
|
||||
let content = Reflect::call(&vault, &"read".into(), &path.into())?;
|
||||
content.as_string().ok_or_else(|| mize_err!("Failed to read file"))
|
||||
}
|
||||
|
||||
/// Send content to Discord channel using Discord REST API
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
pub fn send_to_discord_rest(&self, content: String, channel_id: u64) -> MizeResult<()> {
|
||||
// TODO: Implement using web_sys to call Discord REST API
|
||||
// 1. Get token: mize.get_config("discord.token")
|
||||
// 2. POST to https://discord.com/api/v10/channels/{channel_id}/messages
|
||||
// 3. Headers: Authorization: Bot <token>, Content-Type: application/json
|
||||
// 4. Body: {"content": content}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ========== Native (OS daemon) methods ==========
|
||||
|
||||
/// Start the serenity Discord bot
|
||||
#[cfg(feature = "target-os")]
|
||||
pub async fn start_serenity_bot(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
let token = mize.get_config("discord.token")?.value_string()?;
|
||||
|
||||
// Define event handler
|
||||
struct Handler;
|
||||
|
||||
#[serenity_async_trait]
|
||||
impl EventHandler for Handler {
|
||||
async fn ready(&self, _: Context, ready: Ready) {
|
||||
println!("Discord bot logged in as {}", ready.user.name);
|
||||
}
|
||||
|
||||
// TODO: Add message handler for receiving commands from plugin
|
||||
// This is where you'll implement communication with the Obsidian plugin
|
||||
// Example:
|
||||
// async fn message(&self, ctx: Context, msg: Message) {
|
||||
// if msg.content.starts_with("!send") {
|
||||
// // Parse command and send file to channel
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
let mut client = Client::builder(&token, GatewayIntents::non_privileged())
|
||||
.event_handler(Handler)
|
||||
.await?;
|
||||
|
||||
// Spawn the client in a tokio task
|
||||
tokio::spawn({
|
||||
let client_clone = client.clone();
|
||||
async move {
|
||||
if let Err(why) = client_clone.start().await {
|
||||
eprintln!("Discord client error: {:?}", why);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.client = Some(client);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MizePart for DiscordPart {
|
||||
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
// WASM: Add command to Obsidian
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
{
|
||||
let obsidian_part = mize.get_part_native::<ObsidianPart>("obsidian")?;
|
||||
|
||||
let callback = {
|
||||
let mize_clone = mize.clone();
|
||||
move |mize_inner: Mize| -> MizeResult<()> {
|
||||
// Get configuration values
|
||||
let file_path = mize_inner.get_config("sc2.shared_daily_journal_file")?
|
||||
.value_string()?;
|
||||
let channel_id_str = mize_inner.get_config("sc2.shared_daily_journal_channel")?
|
||||
.value_string()?;
|
||||
let channel_id = channel_id_str.parse::<u64>()?;
|
||||
|
||||
// Get DiscordPart and send the file
|
||||
let mut mize_mut = mize_inner;
|
||||
let discord_part = mize_mut.get_part_native::<DiscordPart>("discord")?;
|
||||
let content = discord_part.read_obsidian_file(&file_path)?;
|
||||
|
||||
// TODO: COMMUNICATION WITH DAEMON GOES HERE
|
||||
// For now, we send directly using REST API.
|
||||
// Replace this with your plugin-to-daemon messaging logic.
|
||||
discord_part.send_to_discord_rest(content, channel_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
obsidian_part.add_command(MartsCommand {
|
||||
id: "send_to_discord".to_string(),
|
||||
name: "Send to Discord".to_string(),
|
||||
callback: Box::new(callback),
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Native: Override async_init to start serenity bot
|
||||
#[cfg(feature = "target-os")]
|
||||
async fn async_init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
self.start_serenity_bot(mize).await
|
||||
}
|
||||
}
|
||||
|
||||
impl MizePartCreate for DiscordPart {}
|
||||
|
||||
pub fn part_discord(mize: &mut Mize) -> MizeResult<()> {
|
||||
mize.register_part(Box::new(DiscordPart {
|
||||
mize: mize.clone(),
|
||||
#[cfg(feature = "target-os")]
|
||||
client: None,
|
||||
}))
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult};
|
||||
use reqwest::{blocking::Client, blocking::Response, header, Method};
|
||||
use serde_json::{json, Value};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
pub struct Habitica {
|
||||
mize: Mize,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
pub fn habitica(mize: &mut Mize) -> MizeResult<()> {
|
||||
let client = Client::new();
|
||||
mize.register_part(Box::new(Habitica {
|
||||
mize: mize.clone(),
|
||||
client,
|
||||
}))
|
||||
}
|
||||
|
||||
impl MizePartCreate for Habitica {}
|
||||
impl MizePart for Habitica {
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
mize.new_opt("habitica.api_url");
|
||||
mize.new_opt("habitica.user_id");
|
||||
mize.new_opt("habitica.api_token");
|
||||
mize.new_opt("habitica.client_name");
|
||||
}
|
||||
}
|
||||
|
||||
impl Habitica {
|
||||
pub fn api_request(&mut self, method: Method, path: String, data: Value) -> MizeResult<Value> {
|
||||
let api_url = self.mize.get_config("habitica.api_url")?.to_string();
|
||||
let user_id = self.mize.get_config("habitica.user_id")?.to_string();
|
||||
let api_token = self.mize.get_config("habitica.api_token")?.to_string();
|
||||
let client_name = self.mize.get_config("habitica.client_name")?.to_string();
|
||||
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert(
|
||||
"Content-Type",
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
);
|
||||
headers.insert("x-api-user", header::HeaderValue::from_str(&user_id)?);
|
||||
headers.insert("x-api-key", header::HeaderValue::from_str(&api_token)?);
|
||||
headers.insert("x-client", header::HeaderValue::from_str(&client_name)?);
|
||||
|
||||
let url = format!("{}/{}", api_url, path.trim_start_matches('/'));
|
||||
|
||||
let mut request_builder = self.client.request(method, &url).headers(headers);
|
||||
|
||||
if data != json!({}) {
|
||||
request_builder = request_builder.json(&data);
|
||||
}
|
||||
|
||||
let response = request_builder.send()?;
|
||||
|
||||
handle_rate_limit(&response);
|
||||
|
||||
if !response.status().is_success() {
|
||||
let status = response.status();
|
||||
let text = response.text().unwrap_or_default();
|
||||
log::info!("request_url: {}", url);
|
||||
return Err(mize_err!(
|
||||
"Habitica API error: {} {} - {}",
|
||||
status.as_u16(),
|
||||
status.canonical_reason().unwrap_or(""),
|
||||
text
|
||||
));
|
||||
}
|
||||
|
||||
let json_response: Value = response.json()?;
|
||||
Ok(json_response.get("data").cloned().unwrap_or(Value::Null))
|
||||
}
|
||||
|
||||
pub fn get_tasks(&mut self, task_type: &str) -> MizeResult<Value> {
|
||||
self.api_request(
|
||||
Method::GET,
|
||||
format!("tasks/user?type={}", task_type),
|
||||
json!({}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn delete_task(&mut self, id: &str) -> MizeResult<Value> {
|
||||
self.api_request(Method::DELETE, format!("tasks/{}", id), json!({}))
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_rate_limit(response: &Response) {
|
||||
let headers = response.headers();
|
||||
let limit = headers
|
||||
.get("X-RateLimit-Limit")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.unwrap_or("NONE");
|
||||
let remaining = headers
|
||||
.get("X-RateLimit-Remaining")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|s| s.parse::<i32>().ok())
|
||||
.unwrap_or(10);
|
||||
let reset = headers
|
||||
.get("X-RateLimit-Reset")
|
||||
.and_then(|v| v.to_str().ok());
|
||||
|
||||
log::info!(
|
||||
"RateLimit: {} | Remaining: {} | Reset: {}",
|
||||
limit,
|
||||
remaining,
|
||||
reset.unwrap_or("N/A")
|
||||
);
|
||||
|
||||
if remaining < 2 {
|
||||
if let Some(reset_str) = reset {
|
||||
if let Ok(reset_date) = DateTime::parse_from_rfc2822(reset_str) {
|
||||
let now = Utc::now();
|
||||
let wait_duration = reset_date.signed_duration_since(now).to_std();
|
||||
if let Ok(wait_duration) = wait_duration {
|
||||
let wait_ms = wait_duration.as_millis() as u64 + 1000;
|
||||
log::info!(
|
||||
"Waiting {} secs for next rate limit window...",
|
||||
(wait_ms / 1000)
|
||||
);
|
||||
sleep(Duration::from_millis(wait_ms));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from "./task/obsidian-otask-parse";
|
||||
export * from "./c2vi/obsidian-canvas-patch";
|
||||
|
||||
export * from "./js/stub_deno";
|
||||
@@ -1,53 +0,0 @@
|
||||
use deno_core::extension;
|
||||
use deno_core::op2;
|
||||
use deno_core::OpState;
|
||||
use mize::{Mize, MizeError};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::CliPart;
|
||||
use crate::JsPart;
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_mize_get_part(#[string] name: &str) {
|
||||
log::info!("js wants the part {}", name);
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
fn op_mize_get_config(state: &mut OpState, #[string] key: &str) -> String {
|
||||
let mize: Mize = state.borrow::<Mize>().clone();
|
||||
format!("config_value_for_{}", key)
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_mize_add_part(state: &mut OpState, #[string] key: &str) {
|
||||
let mize: Mize = state.borrow::<Mize>().clone();
|
||||
log::info!("adding part {}", key);
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_cli_subcommand(state: &mut OpState, #[string] name: String) {
|
||||
let mut mize: Mize = state.borrow::<Mize>().clone();
|
||||
let mut cli = mize.get_part_native::<CliPart>("cli").unwrap();
|
||||
cli.subcommand(
|
||||
clap::Command::new(name.clone()),
|
||||
move |_sub_matches, mut mize| {
|
||||
let mut js = mize.get_part_native::<JsPart>("js").unwrap();
|
||||
js.eval("log(\"cli command being called\")".to_string())?;
|
||||
js.eval(format!("mize.vars.cli.subcommand({name})"))?;
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Create an extension with your custom ops
|
||||
extension!(
|
||||
my_extension,
|
||||
ops = [op_mize_get_part, op_mize_get_config],
|
||||
options = {
|
||||
mize: Mize,
|
||||
},
|
||||
state = |state: &mut OpState, config: Config| {
|
||||
state.put::<Mize>(config.mize);
|
||||
},
|
||||
);
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Mize } from "./stub_deno.ts";
|
||||
|
||||
function argsToMessage(...args) {
|
||||
return args.map((arg) => JSON.stringify(arg)).join(" ");
|
||||
}
|
||||
|
||||
globalThis.log = (...args) => {
|
||||
Deno.core.print(`[out]: ${argsToMessage(...args)}\n`, false);
|
||||
};
|
||||
|
||||
globalThis.mize = new Mize();
|
||||
@@ -1,136 +0,0 @@
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::url::Url;
|
||||
use deno_core::v8::OneByteConst;
|
||||
use deno_core::{
|
||||
ascii_str_include, extension, op2, JsRuntime, ModuleSpecifier, PollEventLoopOptions,
|
||||
RuntimeOptions,
|
||||
};
|
||||
use deno_core::{FastStaticString, FsModuleLoader};
|
||||
use mize::async_trait;
|
||||
use mize::instance::MizePartCreate;
|
||||
use mize::{mize_part, Mize, MizePart, MizeResult};
|
||||
use std::future::Future;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::task::{spawn_local, LocalSet};
|
||||
|
||||
mod glue_deno;
|
||||
|
||||
#[mize_part("js")]
|
||||
#[derive(Default)]
|
||||
pub struct JsPart {
|
||||
mize: Mize,
|
||||
sender: Option<flume::Sender<JsRuntimeThreadMessage>>,
|
||||
}
|
||||
|
||||
pub type BoxFuture<'a> = Pin<Box<dyn Future<Output = MizeResult<()>> + 'a>>;
|
||||
pub type BoxClosure = Box<dyn for<'a> FnOnce(&'a mut JsRuntime) -> BoxFuture<'a> + Send + 'static>;
|
||||
|
||||
impl MizePart for JsPart {
|
||||
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
log::info!("js part run");
|
||||
self.sender
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(JsRuntimeThreadMessage::DoRunPhase)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn js(mize: &mut Mize) -> MizeResult<()> {
|
||||
let (sender, receiver) = flume::unbounded::<JsRuntimeThreadMessage>();
|
||||
let mize_clone = mize.clone();
|
||||
|
||||
// the thread which will run any js
|
||||
mize.spawn_and_wait("js_runtime_thread", || {
|
||||
js_runtime_thread(mize_clone, receiver)
|
||||
})?;
|
||||
|
||||
mize.add_part(Box::new(JsPart {
|
||||
mize: mize.clone(),
|
||||
sender: Some(sender),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn part_from_file(
|
||||
mize: &mut Mize,
|
||||
name: &'static str,
|
||||
code: FastStaticString,
|
||||
) -> MizeResult<()> {
|
||||
let mut js = mize.get_part_native::<JsPart>("js")?;
|
||||
js.execute_init_js(code)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl JsPart {
|
||||
pub fn execute_init_js(&mut self, code: FastStaticString) -> MizeResult<()> {
|
||||
self.sender
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(JsRuntimeThreadMessage::RunInitJs(code))
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
pub fn eval(&mut self, code: String) -> MizeResult<()> {
|
||||
self.sender
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(JsRuntimeThreadMessage::RunJs(code))
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn js_runtime_thread(
|
||||
mize: Mize,
|
||||
receiver: flume::Receiver<JsRuntimeThreadMessage>,
|
||||
) -> MizeResult<()> {
|
||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
//module_loader: Some(Rc::new(FsModuleLoader)),
|
||||
//extensions: vec![glue_deno::my_extension::init(mize.clone())],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
js_runtime
|
||||
.execute_script("[stub]", ascii_str_include!("../deno_dist/glue_deno.js"))
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
log::info!("js thread waiting for smth");
|
||||
let msg = receiver.recv().unwrap();
|
||||
log::info!("js thread got smth");
|
||||
match msg {
|
||||
JsRuntimeThreadMessage::RunInitJs(js_code) => {
|
||||
if let Err(err) = js_runtime.execute_script("[init]", js_code) {
|
||||
log::info!("err: {}", err);
|
||||
}
|
||||
log::info!("done running js");
|
||||
}
|
||||
JsRuntimeThreadMessage::RunJs(js_code) => {
|
||||
if let Err(err) = js_runtime.execute_script("[idk]", js_code) {
|
||||
log::info!("err: {}", err);
|
||||
}
|
||||
log::info!("done running js");
|
||||
}
|
||||
JsRuntimeThreadMessage::DoRunPhase => {
|
||||
if let Err(err) = js_runtime.execute_script("[runPhase]", "mize.runPhase()") {
|
||||
log::info!("err: {}", err);
|
||||
}
|
||||
log::info!("done with runPhase");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum JsRuntimeThreadMessage {
|
||||
//Closure(Box<dyn FnOnce(&mut JsRuntime) -> MizeResult<()> + Send>),
|
||||
//AsyncClosure(BoxClosure),
|
||||
RunInitJs(FastStaticString),
|
||||
RunJs(String),
|
||||
DoRunPhase,
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
export class Mize {
|
||||
async get_config(key: string): Promise<any> {
|
||||
return "hiiiiiii";
|
||||
}
|
||||
async add_opts(opts: any): Promise<void> {}
|
||||
async add_part(part: any): Promise<void> {}
|
||||
async report_err(err: MizeError) {}
|
||||
runPhase() {
|
||||
Deno.core.print("runPhase in js \n");
|
||||
}
|
||||
}
|
||||
|
||||
type MizeError = string;
|
||||
|
||||
export class Cli {
|
||||
constructor(mize: Mize) {}
|
||||
async sub_command(name: string, cmd: any): Promise<void> {}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#![feature(oneshot_channel)]
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub mod cli;
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use cli::*;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub mod habitica;
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use habitica::*;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub mod c2vi {
|
||||
pub mod part;
|
||||
pub mod dashboard;
|
||||
}
|
||||
pub use c2vi::part::*;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub mod dev;
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use dev::part_dev;
|
||||
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
pub mod obsidian;
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
pub use obsidian::*;
|
||||
|
||||
pub mod spacetime;
|
||||
pub use spacetime::*;
|
||||
|
||||
pub mod auth {
|
||||
pub mod client;
|
||||
pub use client::*;
|
||||
}
|
||||
pub use auth::*;
|
||||
|
||||
pub mod ppc;
|
||||
|
||||
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
pub mod matrix;
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
pub use matrix::part_matrix;
|
||||
|
||||
pub mod sci;
|
||||
|
||||
//#[cfg(feature = "target-os")]
|
||||
//pub mod js;
|
||||
//#[cfg(feature = "target-os")]
|
||||
//pub use js::*;
|
||||
|
||||
pub use generated;
|
||||
@@ -1,247 +0,0 @@
|
||||
use mize::{Mize, MizeResult, MizePart, MizePartCreate, mize_part};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use js_sys::Function;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
use matrix_sdk::{
|
||||
Client, ClientConfig, SyncState,
|
||||
ruma::{
|
||||
user_id::UserId,
|
||||
events::room::message::RoomMessageEventContent,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(feature = "target-js")]
|
||||
mod ui;
|
||||
use ui::mount_chat_app;
|
||||
|
||||
const ROOM_ID: &str = "!XwqdXGPlmaLatdGUiN:ppc.social";
|
||||
const ACCOUNT_NAME: &str = "default";
|
||||
const INITIAL_LOAD_LIMIT: usize = 50;
|
||||
|
||||
/// Matrix part that manages Matrix client and UI
|
||||
#[mize_part("matrix")]
|
||||
#[derive(Clone)]
|
||||
pub struct MatrixPart {
|
||||
mize: Mize,
|
||||
pub client: Option<Client>,
|
||||
pub timeline: Vec<matrix_sdk::SyncRoomEvent>,
|
||||
pub message_draft: String,
|
||||
pub edit_event_id: Option<String>,
|
||||
}
|
||||
|
||||
impl MizePart for MatrixPart {}
|
||||
impl MizePartCreate for MatrixPart {}
|
||||
|
||||
impl MatrixPart {
|
||||
/// Create a new MatrixPart
|
||||
pub fn new(mize: Mize) -> Self {
|
||||
Self {
|
||||
mize,
|
||||
client: None,
|
||||
timeline: Vec::new(),
|
||||
message_draft: String::new(),
|
||||
edit_event_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the Matrix client from config
|
||||
pub async fn init_client(&mut self) -> Result<(), String> {
|
||||
let homeserver = self.mize
|
||||
.get_config(&format!("matrix.accounts.{}.homeserver", ACCOUNT_NAME))
|
||||
.map_err(|e| format!("Failed to get homeserver config: {:?}", e))?;
|
||||
|
||||
let access_token = self.mize
|
||||
.get_config(&format!("matrix.accounts.{}.access_token", ACCOUNT_NAME))
|
||||
.map_err(|e| format!("Failed to get access_token config: {:?}", e))?;
|
||||
|
||||
let user_id_str = self.mize
|
||||
.get_config(&format!("matrix.accounts.{}.user_id", ACCOUNT_NAME))
|
||||
.map_err(|e| format!("Failed to get user_id config: {:?}", e))?;
|
||||
|
||||
let device_id = self.mize
|
||||
.get_config(&format!("matrix.accounts.{}.device_id", ACCOUNT_NAME))
|
||||
.map_err(|e| format!("Failed to get device_id config: {:?}", e))?;
|
||||
|
||||
let user_id = UserId::parse(&user_id_str)
|
||||
.map_err(|e| format!("Invalid user_id: {:?}", e))?;
|
||||
|
||||
let config = ClientConfig::new()
|
||||
.homeserver_url(homeserver)
|
||||
.device_id(device_id);
|
||||
|
||||
let client = Client::new_with_config(user_id, Some(access_token), config)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to create client: {:?}", e))?;
|
||||
|
||||
self.client = Some(client.clone());
|
||||
|
||||
// Start sync in background
|
||||
self.start_sync(client).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start sync and set up event listeners
|
||||
async fn start_sync(&mut self, client: Client) -> Result<(), String> {
|
||||
let room_id = ROOM_ID.to_string();
|
||||
let mize = self.mize.clone();
|
||||
|
||||
// Start sync
|
||||
let sync_token = client.sync_token().await;
|
||||
let mut sync_state = SyncState::new(sync_token);
|
||||
|
||||
let client_clone = client.clone();
|
||||
|
||||
spawn_local(async move {
|
||||
loop {
|
||||
match client_clone.sync(&mut sync_state).await {
|
||||
Ok(response) => {
|
||||
// Process sync response - update MatrixPart via mize
|
||||
if let Some(rooms) = response.rooms {
|
||||
if let Some(join) = rooms.join {
|
||||
if let Some(room_events) = join.get(&room_id) {
|
||||
// Get MatrixPart and update timeline
|
||||
if let Ok(mut matrix_part) = mize.get_part_native::<MatrixPart>("matrix") {
|
||||
for event in &room_events.timeline.events {
|
||||
if event.room_id() == room_id {
|
||||
matrix_part.timeline.push(event.clone());
|
||||
// Keep only last INITIAL_LOAD_LIMIT events
|
||||
if matrix_part.timeline.len() > INITIAL_LOAD_LIMIT {
|
||||
matrix_part.timeline.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should stop syncing
|
||||
if sync_state.should_stop() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
console_log!("Sync error: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a message to the room
|
||||
pub async fn send_message(&mut self, room_id: &str, content: Option<>) -> MizeResult<()> {
|
||||
let client = self.client.as_ref()
|
||||
.ok_or_else(|| mize::error::Error::new("Client not initialized"))?;
|
||||
|
||||
if self.message_draft.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let content = RoomMessageEventContent::text_plain(&self.message_draft);
|
||||
|
||||
if let Some(_event_id) = &self.edit_event_id {
|
||||
// Editing existing message - placeholder for now
|
||||
console_log!("Editing message (not yet implemented)");
|
||||
}
|
||||
|
||||
// Send message
|
||||
client.send_room_message(room_id, content).await?;
|
||||
|
||||
// Clear draft after sending
|
||||
self.message_draft.clear();
|
||||
self.edit_event_id = None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry point for the Matrix part
|
||||
pub fn part_matrix(mize: &mut Mize) -> MizeResult<()> {
|
||||
#[cfg(feature = "target-obsidian")] {
|
||||
use crate::ObsidianPart;
|
||||
|
||||
// Get the Obsidian part
|
||||
let obsidian = mize.get_part_native::<ObsidianPart>("obsidian")?;
|
||||
|
||||
// Create and register MatrixPart
|
||||
let matrix_part = MatrixPart::new(mize.clone());
|
||||
mize.register_part(Box::new(matrix_part));
|
||||
|
||||
// Get the registered MatrixPart to initialize it
|
||||
let mize_clone = mize.clone();
|
||||
spawn_local(async move {
|
||||
if let Ok(mut matrix_part) = mize_clone.get_part_native::<MatrixPart>("matrix") {
|
||||
if let Err(e) = matrix_part.init_client().await {
|
||||
console_log!("Failed to initialize Matrix client: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Create a JS function that will be called when the view is opened
|
||||
let mize_for_view = mize.clone();
|
||||
let view_creator = Closure::<dyn Fn(JsValue)>::wrap(Box::new(move |leaf: JsValue| {
|
||||
if let Some(element) = extract_dom_element(leaf) {
|
||||
// Get MatrixPart state for the UI
|
||||
if let Ok(matrix_part) = mize_for_view.get_part_native::<MatrixPart>("matrix") {
|
||||
let timeline = matrix_part.timeline.clone();
|
||||
let message_draft = matrix_part.message_draft.clone();
|
||||
let edit_event_id = matrix_part.edit_event_id.clone();
|
||||
|
||||
mount_chat_app(element, timeline, message_draft, edit_event_id, mize_for_view.clone());
|
||||
}
|
||||
} else {
|
||||
console_log!("Failed to extract DOM element from leaf");
|
||||
}
|
||||
}));
|
||||
|
||||
// Convert closure to JS Function
|
||||
let view_creator_fn: &Function = view_creator.as_ref().unchecked_ref();
|
||||
|
||||
// Register the view with Obsidian
|
||||
ObsidianPart::register_view(&obsidian.plugin(), "matrix-chat-view", view_creator_fn)?;
|
||||
|
||||
// Prevent the closure from being dropped
|
||||
view_creator.forget();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Extract DOM element from Obsidian leaf JSValue
|
||||
fn extract_dom_element(leaf: JsValue) -> Option<HtmlElement> {
|
||||
use wasm_bindgen::JsCast;
|
||||
use js_sys::Reflect;
|
||||
|
||||
// Try to get the containerEl from the leaf
|
||||
let container_value = Reflect::get(&leaf, &"containerEl".into()).ok()?;
|
||||
let container = container_value.dyn_into::<HtmlElement>().ok()?;
|
||||
|
||||
// Create a div to mount into
|
||||
let doc = web_sys::window()?.document()?;
|
||||
let mount_point = doc.create_element("div").ok()?;
|
||||
mount_point.set_attribute("style", "height: 100%; width: 100%;").ok()?;
|
||||
|
||||
container.append_child(&mount_point).ok()?;
|
||||
mount_point.dyn_into::<HtmlElement>().ok()
|
||||
}
|
||||
|
||||
// Console log macros
|
||||
#[macro_export]
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => {
|
||||
web_sys::console::log_1(&format!($($t)*).into());
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! console_error {
|
||||
($($t:tt)*) => {
|
||||
web_sys::console::error_1(&format!($($t)*).into());
|
||||
};
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_web::events::FormEvent;
|
||||
use mize::Mize;
|
||||
use matrix_sdk::SyncRoomEvent;
|
||||
|
||||
const ROOM_ID: &str = "!XwqdXGPlmaLatdGUiN:ppc.social";
|
||||
|
||||
/// Props for the chat component
|
||||
#[derive(Props, Clone)]
|
||||
pub struct ChatProps {
|
||||
pub mize: Mize,
|
||||
pub initial_timeline: Vec<SyncRoomEvent>,
|
||||
pub initial_draft: String,
|
||||
pub initial_edit_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Message component - renders a single message
|
||||
#[component]
|
||||
pub fn Message(cx: Scope, event: SyncRoomEvent) -> Element {
|
||||
let sender = event.sender().to_string();
|
||||
let timestamp = event.origin_server_ts();
|
||||
|
||||
// Extract display name from MXID (basic version)
|
||||
let display_name = if let Some(at_pos) = sender.find('@') {
|
||||
if let Some(colon_pos) = sender.rfind(':') {
|
||||
&sender[at_pos + 1..colon_pos]
|
||||
} else {
|
||||
&sender[at_pos + 1..]
|
||||
}
|
||||
} else {
|
||||
&sender
|
||||
};
|
||||
|
||||
// Format timestamp
|
||||
let time_str = {
|
||||
let dt = js_sys::Date::new(&(timestamp as f64));
|
||||
dt.to_locale_string()
|
||||
};
|
||||
|
||||
// Extract message content
|
||||
let content = event
|
||||
.as_original()
|
||||
.and_then(|e| e.content().deserialize().ok())
|
||||
.and_then(|c: matrix_sdk::ruma::events::room::message::RoomMessageEventContent| {
|
||||
c.body().map(|s| s.to_string())
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
// Check if this is an edit
|
||||
let is_edit = event
|
||||
.as_original()
|
||||
.map(|e| {
|
||||
e.content()
|
||||
.get("m.relates_to")
|
||||
.and_then(|v| v.as_object())
|
||||
.and_then(|o| o.get("rel_type"))
|
||||
.and_then(|v| v.as_str())
|
||||
== Some("m.replace")
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "matrix-chat-event",
|
||||
style: "margin: 14px 16px; padding-bottom: 10px; border-bottom: 1px solid var(--background-modifier-border);",
|
||||
|
||||
div {
|
||||
class: "matrix-chat-msghead",
|
||||
style: "font-size: 1em; font-weight: 500; color: var(--interactive-accent); display: flex; align-items: center; gap: 10px;",
|
||||
|
||||
span { display_name }
|
||||
span {
|
||||
style: "font-size: 0.85em; color: var(--text-muted); margin-left: auto;",
|
||||
{time_str.clone()}
|
||||
if is_edit { " · edited" }
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
class: "matrix-chat-msgbody",
|
||||
style: "margin: 6px 0 0 0; font-size: 1em;",
|
||||
{content}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Editor component - message input area
|
||||
#[component]
|
||||
pub fn Editor(cx: Scope, mize: Mize, draft: UseSignal<String>, edit_id: UseSignal<Option<String>>) -> Element {
|
||||
let on_input = move |evt: FormEvent| {
|
||||
draft.set(evt.value.clone());
|
||||
};
|
||||
|
||||
let on_send = move |_| {
|
||||
let mize_clone = mize.clone();
|
||||
let draft_val = draft.read().clone();
|
||||
let edit_id_val = edit_id.read().clone();
|
||||
|
||||
cx.spawn(async move {
|
||||
if let Ok(mut matrix_part) = mize_clone.get_part_native::<crate::matrix::MatrixPart>("matrix") {
|
||||
matrix_part.message_draft = draft_val;
|
||||
matrix_part.edit_event_id = edit_id_val;
|
||||
if let Err(e) = matrix_part.send_message().await {
|
||||
console_log!("Failed to send message: {:?}", e);
|
||||
}
|
||||
// Clear local draft
|
||||
draft.set(String::new());
|
||||
edit_id.set(None);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let on_keydown = move |evt: dioxus_web::events::KeyboardEvent| {
|
||||
if (evt.ctrl_key() || evt.meta_key()) && evt.key() == "Enter" {
|
||||
on_send(evt);
|
||||
}
|
||||
};
|
||||
|
||||
let on_cancel = move |_| {
|
||||
draft.set(String::new());
|
||||
edit_id.set(None);
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "matrix-chat-editor",
|
||||
style: "border-top: 1px solid var(--background-modifier-border); background: var(--background-primary); padding: 8px 12px;",
|
||||
|
||||
div {
|
||||
class: "matrix-chat-editor-inner",
|
||||
style: "border: 1px solid var(--background-modifier-border); border-radius: 6px; overflow: hidden; background: var(--background-secondary);",
|
||||
|
||||
textarea {
|
||||
r#type: "text",
|
||||
rows: "3",
|
||||
style: "width: 99%; min-height: 40px; resize: vertical; background: transparent; border: none; outline: none; padding: 8px; font-family: inherit; font-size: inherit; color: var(--text-normal);",
|
||||
value: "{draft}",
|
||||
oninput: on_input,
|
||||
onkeydown: on_keydown,
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
r#type: "button",
|
||||
style: "margin-top: 3px; float: right; padding: 6px 14px; font-size: 0.97em; border-radius: 5px; border: none; background: var(--interactive-accent); color: white; cursor: pointer;",
|
||||
onclick: on_send,
|
||||
if edit_id.read().is_some() { "Save" } else { "Send" }
|
||||
}
|
||||
|
||||
if edit_id.read().is_some() {
|
||||
button {
|
||||
r#type: "button",
|
||||
style: "margin-right: 12px; padding: 6px 12px; font-size: 0.93em; border-radius: 5px; border: none; color: var(--text-normal); background: var(--background-secondary); cursor: pointer;",
|
||||
onclick: on_cancel,
|
||||
"Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Timeline component - list of messages
|
||||
#[component]
|
||||
pub fn Timeline(cx: Scope, mize: Mize, timeline: UseSignal<Vec<SyncRoomEvent>>) -> Element {
|
||||
// Periodically refresh timeline from MatrixPart
|
||||
cx.use_effect((), move |_| {
|
||||
let mize_clone = mize.clone();
|
||||
let timeline_clone = timeline.clone();
|
||||
|
||||
// Set up interval to poll for updates
|
||||
let interval = js_sys::set_interval(
|
||||
&Closure::<dyn FnMut()>::wrap(Box::new(move || {
|
||||
if let Ok(matrix_part) = mize_clone.get_part_native::<crate::matrix::MatrixPart>("matrix") {
|
||||
*timeline_clone.write() = matrix_part.timeline.clone();
|
||||
}
|
||||
})),
|
||||
1000, // 1 second
|
||||
);
|
||||
|
||||
// Cleanup on unmount
|
||||
move || {
|
||||
js_sys::clear_interval(interval);
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "matrix-chat-scroll",
|
||||
style: "flex-grow: 1; overflow-y: auto; padding: 8px 0; background: var(--background-secondary);",
|
||||
|
||||
// Room name header
|
||||
div {
|
||||
class: "matrix-chat-roomname",
|
||||
style: "font-weight: 600; text-align: center; font-size: 1.1em; margin: 2px 0 12px 0; border-bottom: 1px solid var(--background-modifier-border); color: var(--text-accent); background: var(--background-primary);",
|
||||
"Matrix Chat - {ROOM_ID}"
|
||||
}
|
||||
|
||||
// Messages
|
||||
for event in timeline.read().iter() {
|
||||
rsx! { Message { event: event.clone() } }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Root chat component
|
||||
#[component]
|
||||
pub fn Chat(cx: Scope, props: ChatProps) -> Element {
|
||||
let timeline = use_signal(&cx, || props.initial_timeline);
|
||||
let draft = use_signal(&cx, || props.initial_draft);
|
||||
let edit_id = use_signal(&cx, || props.initial_edit_id);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "matrix-chat-root",
|
||||
style: "height: 100%; display: flex; flex-direction: column; background: var(--background-primary); color: var(--text-normal);",
|
||||
|
||||
// Title
|
||||
div {
|
||||
class: "matrix-chat-title",
|
||||
style: "padding: 12px; font-weight: bold; font-size: 1.2em; text-align: center; border-bottom: 1px solid var(--background-modifier-border);",
|
||||
"Matrix Chat"
|
||||
}
|
||||
|
||||
// Info
|
||||
div {
|
||||
class: "matrix-chat-info",
|
||||
style: "font-size: 0.85em; color: var(--text-muted); text-align: center; margin-bottom: 5px;",
|
||||
"Room: {ROOM_ID}"
|
||||
}
|
||||
|
||||
// Timeline
|
||||
Timeline { mize: props.mize.clone(), timeline }
|
||||
|
||||
// Editor
|
||||
Editor { mize: props.mize.clone(), draft, edit_id }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Mount the Dioxus app into a DOM element
|
||||
pub fn mount_chat_app(
|
||||
element: web_sys::HtmlElement,
|
||||
initial_timeline: Vec<SyncRoomEvent>,
|
||||
initial_draft: String,
|
||||
initial_edit_id: Option<String>,
|
||||
mize: Mize,
|
||||
) {
|
||||
dioxus_web::launch(
|
||||
dioxus::prelude::App::new(move || rsx! {
|
||||
Chat {
|
||||
mize: mize.clone(),
|
||||
initial_timeline: initial_timeline.clone(),
|
||||
initial_draft: initial_draft.clone(),
|
||||
initial_edit_id: initial_edit_id.clone(),
|
||||
}
|
||||
}),
|
||||
element,
|
||||
);
|
||||
}
|
||||
|
||||
// Console log macro
|
||||
#[macro_export]
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => {
|
||||
web_sys::console::log_1(&format!($($t)*).into());
|
||||
};
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
use mize::{Mize, MizeResult, MizePart, MizePartCreate, mize_part};
|
||||
use wasm_bindgen::JsValue;
|
||||
use mize::error::IntoMizeResultJsValue;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use dioxus::prelude::*;
|
||||
use crate::ClientAuth;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use mize::platform::js::JsMize;
|
||||
|
||||
|
||||
// console_log macro
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
macro_rules! console_log {
|
||||
// Note that this is using the `log` function imported above during
|
||||
// `bare_bones`
|
||||
($($t:tt)*) => ({ log(&format_args!($($t)*).to_string())})
|
||||
}
|
||||
|
||||
pub fn part_obsidian(mize: &mut Mize, plugin: JsValue, app: JsValue) -> MizeResult<()> {
|
||||
|
||||
mize.register_part(Box::new(ObsidianPart {
|
||||
mize: mize.clone(),
|
||||
plugin,
|
||||
app,
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[mize_part("obsidian")]
|
||||
#[derive(Default)]
|
||||
pub struct ObsidianPart {
|
||||
mize: Mize,
|
||||
plugin: JsValue, // the jsValue of the obsidian plugin
|
||||
app: JsValue, // the jsValue of the obsidian instance
|
||||
}
|
||||
|
||||
pub struct MartsCommand {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub callback: Box<dyn FnMut(Mize) -> MizeResult<()>>,
|
||||
}
|
||||
|
||||
impl MizePart for ObsidianPart {}
|
||||
impl MizePartCreate for ObsidianPart {}
|
||||
|
||||
impl ObsidianPart {
|
||||
pub fn plugin(&self) -> &JsValue {
|
||||
&self.plugin
|
||||
}
|
||||
pub fn add_command(&self, mut cmd: MartsCommand) -> MizeResult<()> {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use js_sys::{Function, Reflect, Object};
|
||||
let obsidian_cmd = Object::new();
|
||||
|
||||
Reflect::set(&obsidian_cmd, &"id".into(), &"login".into()).mres()?;
|
||||
Reflect::set(&obsidian_cmd, &"name".into(), &"Login".into()).mres()?;
|
||||
|
||||
// callback
|
||||
let mize_clone = self.mize.clone();
|
||||
let cb = Closure::<dyn FnMut()>::wrap(Box::new(move || {
|
||||
if let Err(e) = (cmd.callback)(mize_clone.clone()) {
|
||||
mize_clone.report_err(e);
|
||||
}
|
||||
}));
|
||||
|
||||
Reflect::set(&obsidian_cmd, &"callback".into(), cb.as_ref()).mres()?;
|
||||
|
||||
// plugin.addCommand(cmd)
|
||||
let add_command_fn = Reflect::get(&self.plugin, &"addCommand".into()).mres()?
|
||||
.dyn_into::<Function>().mres()?;
|
||||
|
||||
add_command_fn
|
||||
.call1(&self.plugin, &obsidian_cmd).mres()?;
|
||||
|
||||
|
||||
// IMPORTANT: prevent callback from being dropped
|
||||
cb.forget();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn test_fn(mize: &mut JsMize) {
|
||||
let mize = mize.inner();
|
||||
console_log!("test fn...");
|
||||
|
||||
if let Err(e) = try_test_fn(mize) {
|
||||
console_log!("err: {:?}", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn try_test_fn(mize: &mut Mize) -> MizeResult<()> {
|
||||
//let auth = mize.get_part_native::<ClientAuth>("client_auth")?;
|
||||
//console_log!("secrets: {:?}", auth.secrets());
|
||||
|
||||
let parts = mize.parts.lock().unwrap();
|
||||
for part in parts.keys() {
|
||||
console_log!("part: {}", part)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn part_obsidian_init(mize: &mut JsMize, plugin: JsValue, app: JsValue) {
|
||||
let mut mize = mize.inner();
|
||||
if let Err(e) = obsidian_mize_main(&mut mize, plugin, app) {
|
||||
console_log!("Failed to start the obsidian mize part: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn obsidian_mize_main(mize: &mut Mize, plugin: JsValue, app: JsValue) -> MizeResult<()> {
|
||||
|
||||
crate::part_obsidian(mize, plugin, app)?;
|
||||
crate::part_client_auth(mize)?;
|
||||
crate::spacetime(mize)?;
|
||||
crate::part_matrix(mize)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
|
||||
use std::str::FromStr;
|
||||
use axum::response::Html;
|
||||
use clap::Command;
|
||||
use dioxus::prelude::*;
|
||||
use mize::Mize;
|
||||
use mize::MizeResult;
|
||||
|
||||
pub mod server;
|
||||
|
||||
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())
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use mize::{Mize, MizeError, MizeResult, mize_err};
|
||||
|
||||
use axum::response::Html;
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
use axum::{
|
||||
Json,
|
||||
Router,
|
||||
error_handling::HandleErrorLayer,
|
||||
http::Uri,
|
||||
response::IntoResponse,
|
||||
routing::{any, get},
|
||||
};
|
||||
use axum_oidc::handle_oidc_redirect;
|
||||
use axum_oidc::{
|
||||
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer,
|
||||
OidcRpInitiatedLogout, error::MiddlewareError,
|
||||
};
|
||||
use openidconnect::Scope;
|
||||
use openidconnect::{ClientId, ClientSecret, IssuerUrl};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_sessions::{
|
||||
Expiry, MemoryStore, SessionManagerLayer,
|
||||
cookie::{SameSite, time::Duration},
|
||||
};
|
||||
|
||||
pub fn part_ppc_server(mize: &mut Mize) -> MizeResult<()> {
|
||||
mize.add_name_only_part("ppc.server");
|
||||
|
||||
let mize_clone = mize.clone();
|
||||
mize.spawn_async("ppc.server_runner", start_server(mize_clone));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start_server(mut mize: Mize) -> MizeResult<()> {
|
||||
dioxus::logger::initialize_default();
|
||||
|
||||
let issuer = mize.get_config("auth.server.issuer")?.value_string()?;
|
||||
let client_id = mize.get_config("auth.server.client_id")?.value_string()?;
|
||||
let client_secret = mize.get_config("auth.server.client_secret")?.value_string()?;
|
||||
let url = mize.get_config("web.url")?.value_string()?;
|
||||
let redirect_url = format!("{url}/oidc");
|
||||
|
||||
let session_store = MemoryStore::default();
|
||||
let session_layer = SessionManagerLayer::new(session_store)
|
||||
.with_secure(false)
|
||||
.with_same_site(SameSite::Lax)
|
||||
.with_expiry(Expiry::OnInactivity(Duration::seconds(120)));
|
||||
|
||||
let oidc_login_service = ServiceBuilder::new()
|
||||
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
||||
dbg!(&e);
|
||||
e.into_response()
|
||||
}))
|
||||
.layer(OidcLoginLayer::<EmptyAdditionalClaims>::new());
|
||||
|
||||
let oidc_client = OidcClient::<EmptyAdditionalClaims>::builder()
|
||||
.with_default_http_client()
|
||||
.with_redirect_url(Uri::from_str(redirect_url.as_str())?)
|
||||
.with_client_id(ClientId::new(client_id))
|
||||
.add_scope(Scope::new("profile".into()))
|
||||
.add_scope(Scope::new("email".into()))
|
||||
// Optional: add untrusted audiences. If the `aud` claim contains any of these audiences, the token is rejected.
|
||||
//.add_untrusted_audience(Audience::new("123456789".to_string()))
|
||||
.with_client_secret(ClientSecret::new(client_secret))
|
||||
.discover(IssuerUrl::new(issuer.into()).expect("Invalid IssuerUrl"))
|
||||
.await
|
||||
.unwrap()
|
||||
.build();
|
||||
|
||||
let oidc_auth_service = ServiceBuilder::new()
|
||||
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
||||
dbg!(&e);
|
||||
e.into_response()
|
||||
}))
|
||||
.layer(OidcAuthLayer::new(oidc_client));
|
||||
|
||||
let app = Router::new()
|
||||
.route("/foo", get(authenticated))
|
||||
.route("/logout", get(logout))
|
||||
.layer(oidc_login_service)
|
||||
.route("/bar", get(maybe_authenticated))
|
||||
.route("/oidc", any(handle_oidc_redirect::<EmptyAdditionalClaims>))
|
||||
.layer(oidc_auth_service)
|
||||
.layer(session_layer)
|
||||
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||
// not authenticated routes
|
||||
.route("/", get(crate::ppc::main_page()))
|
||||
.route("/mc", get(crate::ppc::mc_page()))
|
||||
// matrix .well-known
|
||||
.route(
|
||||
"/.well-known/matrix/client",
|
||||
get(handle_matrix_client).layer(tower_http::cors::CorsLayer::permissive()),
|
||||
)
|
||||
.route(
|
||||
"/.well-known/matrix/server",
|
||||
get(handle_matrix_server),
|
||||
);
|
||||
|
||||
tracing::info!("Running on http://localhost:3000");
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:8010").await.unwrap();
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn authenticated(claims: OidcClaims<EmptyAdditionalClaims>) -> impl IntoResponse {
|
||||
format!("Hello {}", claims.subject().as_str())
|
||||
}
|
||||
|
||||
async fn maybe_authenticated(
|
||||
claims: Result<OidcClaims<EmptyAdditionalClaims>, axum_oidc::error::ExtractorError>,
|
||||
) -> impl IntoResponse {
|
||||
if let Ok(claims) = claims {
|
||||
format!(
|
||||
"Hello {}! You are already logged in from another Handler.",
|
||||
claims.subject().as_str()
|
||||
)
|
||||
} else {
|
||||
"Hello anon!".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
async fn logout(logout: OidcRpInitiatedLogout) -> impl IntoResponse {
|
||||
logout.with_post_logout_redirect(Uri::from_static("https://localhost:3000"))
|
||||
}
|
||||
|
||||
async fn handle_matrix_client() -> Json<Value> {
|
||||
Json(json!({
|
||||
"m.homeserver": {
|
||||
"base_url": "https://matrix.ppc.social"
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
async fn handle_matrix_server() -> Json<Value> {
|
||||
Json(json!({
|
||||
"m.server": "matrix.ppc.social:443"
|
||||
}))
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
pub use mize::{Mize, MizeResult, MizeError};
|
||||
|
||||
pub fn part_sci_inbox(mize: &mut Mize) -> MizeResult<()> {
|
||||
|
||||
mize.add_name_only_part("sci.inbox");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
|
||||
use mize::*;
|
||||
pub mod timetrack;
|
||||
use clap::Command;
|
||||
|
||||
pub fn part_sci(mize: &mut Mize) -> MizeResult<()> {
|
||||
part_add_thought(mize)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn part_add_thought(mize: &mut Mize) -> MizeResult<()> {
|
||||
use crate::generated::spacetime::add_thought;
|
||||
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
|
||||
cli.set_command("sci");
|
||||
cli.subcommand(Command::new("inputThought").alias("ti"), |_matches, mut mize| {
|
||||
|
||||
let tmp_file = std::env::temp_dir().join(format!(
|
||||
"sc2-input-thought-{}.txt",
|
||||
chrono::Utc::now().timestamp()
|
||||
));
|
||||
crate::cli_spawn(&mut mize, format!("nvim {}", tmp_file.display()))?;
|
||||
let new_file_content = std::fs::read_to_string(&tmp_file)
|
||||
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
|
||||
let _ = std::fs::remove_file(&tmp_file);
|
||||
let spacetime = mize.get_part_native::<crate::SpacetimePart>("spacetime")?;
|
||||
spacetime.con().reducers.add_thought(new_file_content)?;
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,640 +0,0 @@
|
||||
use std::sync::oneshot;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use generated::spacetime::{Profile, TaskPart};
|
||||
use mize::out;
|
||||
use mize::proto::MizeMessage;
|
||||
use mize::{Mize, MizeError, MizePart, MizePartCreate, MizeResult, mize_err, mize_part};
|
||||
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::{
|
||||
Action, ActivitiesTableAccess, Activity, DbConnection, TaskPartTableAccess,
|
||||
activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess,
|
||||
update_activity, update_upcoming,
|
||||
};
|
||||
use crate::spacetime::SpacetimePart;
|
||||
use spacetimedb_sdk::TableWithPrimaryKey;
|
||||
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
pub struct TimeTrack {
|
||||
mize: Mize,
|
||||
}
|
||||
|
||||
pub fn part_timetrack(mize: &mut Mize) -> MizeResult<()> {
|
||||
part_timetrack_export(mize)?;
|
||||
|
||||
#[cfg(feature = "target-os")] {
|
||||
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
|
||||
cli.set_command("sci");
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("editActionList").alias("aal").arg(
|
||||
Arg::new("defaultTemplate")
|
||||
.short('d')
|
||||
.action(ArgAction::SetTrue),
|
||||
),
|
||||
command_edit_action_list,
|
||||
)?;
|
||||
cli.subcommand(Command::new("nextAction").alias("ne").arg(
|
||||
Arg::new("break")
|
||||
.short('b')
|
||||
.action(ArgAction::SetTrue),
|
||||
).arg(
|
||||
Arg::new("action")
|
||||
.trailing_var_arg(true)
|
||||
.allow_hyphen_values(true)
|
||||
.num_args(0..), // Accepts 0 or more arguments
|
||||
), command_next_action)?;
|
||||
cli.subcommand(
|
||||
Command::new("printCurrentAction").alias("cu"),
|
||||
command_current_action,
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("listActions").alias("la"),
|
||||
command_list_actions,
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("listActivities").alias("lac"),
|
||||
command_list_activities,
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("editActivities").alias("eac"),
|
||||
command_edit_activities,
|
||||
)?;
|
||||
mize.add_listener(
|
||||
"timetrack.sub_current",
|
||||
Box::new(timetrack_sub_current_listener),
|
||||
)?;
|
||||
|
||||
|
||||
}
|
||||
mize.register_part(Box::new(TimeTrack { mize: mize.clone() }))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_current_update_msg(conn_id: u64, data: TaskPart, mize: &mut Mize) -> MizeResult<()> {
|
||||
let now = fixed_len(data.current.name.clone(), 48);
|
||||
let next = fixed_len(
|
||||
data.upcoming
|
||||
.iter()
|
||||
.nth(0)
|
||||
.unwrap_or(&Action {
|
||||
name: "".to_string(),
|
||||
activity: None,
|
||||
task: None,
|
||||
start: None,
|
||||
end: None,
|
||||
duration: None,
|
||||
})
|
||||
.name
|
||||
.clone(),
|
||||
48,
|
||||
);
|
||||
let msg = MizeMessage::new_custom(
|
||||
"timetrack.current",
|
||||
format!(" {} NEXT: {} ", now, next),
|
||||
conn_id,
|
||||
);
|
||||
let con = mize.get_connection(conn_id)?;
|
||||
con.send(msg)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fixed_len(mut buf: String, len: usize) -> String {
|
||||
if buf.len() > len {
|
||||
buf.truncate(len - 4); // Cut off extra characters
|
||||
buf.push_str(" ...");
|
||||
} else {
|
||||
while buf.len() < len {
|
||||
buf.push(' '); // Fill with whitespace
|
||||
}
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn timetrack_sub_current_listener(msg: MizeMessage, mize: &mut Mize) -> MizeResult<()> {
|
||||
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap();
|
||||
let con = spacetime.con();
|
||||
let mut mize_clone = mize.clone();
|
||||
let mut mize_clone_two = mize.clone();
|
||||
con.subscription_builder()
|
||||
.on_applied(move |ctx| {
|
||||
let data = ctx.db.task_part().identity().find(&ctx.identity()).unwrap();
|
||||
if let Err(e) = send_current_update_msg(msg.conn_id, data, &mut mize_clone) {
|
||||
e.log();
|
||||
}
|
||||
})
|
||||
.add_query(|q| q.from.task_part())
|
||||
.subscribe();
|
||||
con.db
|
||||
.task_part()
|
||||
.on_update(move |_, _old: &TaskPart, new: &TaskPart| {
|
||||
if let Err(e) = send_current_update_msg(msg.conn_id, new.clone(), &mut mize_clone_two) {
|
||||
e.log();
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl MizePartCreate for TimeTrack {}
|
||||
|
||||
impl MizePart for TimeTrack {
|
||||
fn name(&self) -> &'static str {
|
||||
"timetrack"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_next_action(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
let con = spacetime.con();
|
||||
|
||||
let break_flag = matches.get_flag("break");
|
||||
let action = matches.get_many::<String>("action")
|
||||
.map(|vals| vals.map(|s| s.as_str()).collect::<Vec<&str>>().join(" "));
|
||||
let action = match action {
|
||||
Some(action) => Some(Action {
|
||||
name: action.clone(),
|
||||
activity: None,
|
||||
task: None,
|
||||
start: None,
|
||||
end: None,
|
||||
duration: None,
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let old_action = get_current_action(&mut mize, con)?;
|
||||
out!("before action: {}", old_action.name);
|
||||
let (wait_tx, waiter_rx) = std::sync::oneshot::channel::<()>();
|
||||
con.reducers.next_action_then(break_flag, action, |_, _| {
|
||||
wait_tx.send(()).unwrap();
|
||||
})?;
|
||||
waiter_rx.recv()?;
|
||||
|
||||
let new_action = get_current_action(&mut mize, con)?;
|
||||
out!("new action: {}", new_action.name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_current_action(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
let action = get_current_action(&mut mize, spacetime.con())?;
|
||||
out!("{}", action.name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_current_action(_mize: &mut Mize, con: &DbConnection) -> MizeResult<Action> {
|
||||
let (wait_tx, wait_rx) = std::sync::oneshot::channel::<Action>();
|
||||
con.subscription_builder()
|
||||
.on_applied(|ctx| {
|
||||
let task_part_data = ctx.db.task_part().identity().find(&ctx.identity());
|
||||
if task_part_data.is_none() {
|
||||
wait_tx
|
||||
.send(Action {
|
||||
name: "fillier".to_string(),
|
||||
activity: None,
|
||||
task: None,
|
||||
start: None,
|
||||
end: None,
|
||||
duration: None,
|
||||
})
|
||||
.unwrap();
|
||||
} else {
|
||||
wait_tx.send(task_part_data.unwrap().current).unwrap();
|
||||
}
|
||||
})
|
||||
.add_query(|q| {
|
||||
q.from.task_part()
|
||||
//.r#where(|row| row.identity.eq(con.identity()))
|
||||
})
|
||||
.subscribe();
|
||||
let action = wait_rx.recv().unwrap();
|
||||
Ok(action)
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
let activities = get_activities(&mut mize, spacetime.con())?;
|
||||
|
||||
for ac in activities.clone() {
|
||||
print!("ac {}: {}", ac.id, ac.name);
|
||||
if !ac.parents_ids.is_empty() {
|
||||
print!(" -");
|
||||
}
|
||||
for child_id in ac.parents_ids {
|
||||
let child_ac = activities.iter().find(|ac| ac.id == child_id).unwrap();
|
||||
print!(" {}", child_ac.name);
|
||||
}
|
||||
print!("\n")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_list_actions(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
|
||||
let actions = get_actions(spacetime.con())?;
|
||||
|
||||
for action in actions {
|
||||
out!("{}", action.name)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_edit_action_list(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
|
||||
// maybe append a template to the action list
|
||||
if matches.get_flag("defaultTemplate") {
|
||||
out!("Appending template...");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// get list of actions
|
||||
let mut actions = get_actions(spacetime.con())?;
|
||||
|
||||
// write to tmp file
|
||||
let tmp_file_content = actions_to_string(&actions)?;
|
||||
let tmp_file = std::env::temp_dir().join(format!(
|
||||
"ppc-edit-actions-{}.txt",
|
||||
chrono::Utc::now().timestamp()
|
||||
));
|
||||
std::fs::write(&tmp_file, tmp_file_content)
|
||||
.map_err(|e| mize_err!("Failed to write temp file: {}", e))?;
|
||||
|
||||
// open $EDITOR blocking
|
||||
crate::cli_spawn(&mut mize, format!("nvim {}", tmp_file.display()))?;
|
||||
|
||||
// read file and update db
|
||||
let new_file_content = std::fs::read_to_string(&tmp_file)
|
||||
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
|
||||
|
||||
actions = actions_from_string(new_file_content)?;
|
||||
set_actions(actions, spacetime.con())?;
|
||||
|
||||
// delete tmpfile
|
||||
let _ = std::fs::remove_file(&tmp_file);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_actions(con: &DbConnection) -> MizeResult<Vec<Action>> {
|
||||
let (exit_tx, exit_rx) = std::sync::oneshot::channel::<Vec<Action>>();
|
||||
con.subscription_builder()
|
||||
.on_applied(|ctx| {
|
||||
let task_part_data = ctx.db.task_part().identity().find(&ctx.identity());
|
||||
if task_part_data.is_none() {
|
||||
exit_tx.send(Vec::new()).unwrap();
|
||||
} else {
|
||||
exit_tx.send(task_part_data.unwrap().upcoming).unwrap();
|
||||
}
|
||||
})
|
||||
.add_query(|q| {
|
||||
q.from.task_part()
|
||||
//.r#where(|row| row.identity.eq(con.identity()))
|
||||
})
|
||||
.subscribe();
|
||||
let activities = exit_rx.recv().unwrap();
|
||||
Ok(activities)
|
||||
}
|
||||
|
||||
fn set_actions(actions: Vec<Action>, con: &DbConnection) -> MizeResult<()> {
|
||||
let (wait_tx, waiter_rx) = std::sync::oneshot::channel::<()>();
|
||||
con.reducers
|
||||
.update_upcoming_then(actions, |_, _| wait_tx.send(()).unwrap())?;
|
||||
waiter_rx.recv().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn actions_to_string(actions: &Vec<Action>) -> MizeResult<String> {
|
||||
let mut string = String::new();
|
||||
|
||||
for action in actions.iter() {
|
||||
string.push_str(format!("{}\n", action.name).as_str());
|
||||
}
|
||||
|
||||
Ok(string)
|
||||
}
|
||||
|
||||
fn actions_from_string(string: String) -> MizeResult<Vec<Action>> {
|
||||
let mut new_actions = Vec::new();
|
||||
for line in string.lines() {
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut action = Action {
|
||||
name: line.to_owned(),
|
||||
activity: None,
|
||||
task: None,
|
||||
start: None,
|
||||
end: None,
|
||||
duration: None,
|
||||
};
|
||||
parse_action(&mut action);
|
||||
new_actions.push(action);
|
||||
}
|
||||
Ok(new_actions)
|
||||
}
|
||||
|
||||
fn parse_action(action: &mut Action) {
|
||||
let _parts: Vec<&str> = action.name.split_whitespace().collect();
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
|
||||
// get list of activities
|
||||
let activities = get_activities(&mut mize, spacetime.con())?;
|
||||
|
||||
let con = spacetime.con();
|
||||
|
||||
// write to tmp file
|
||||
let mut tmp_file_content = String::new();
|
||||
for ac in activities.iter() {
|
||||
tmp_file_content.push_str(format!("{} {}", ac.id, ac.name).as_str());
|
||||
if !ac.parents_ids.is_empty() {
|
||||
tmp_file_content.push_str(" -");
|
||||
for parent_id in ac.parents_ids.iter() {
|
||||
let parent_ac = activities.iter().find(|ac| ac.id == *parent_id).unwrap();
|
||||
tmp_file_content.push_str(" ");
|
||||
tmp_file_content.push_str(&parent_ac.name);
|
||||
}
|
||||
}
|
||||
tmp_file_content.push_str("\n");
|
||||
}
|
||||
let tmp_file = std::env::temp_dir().join(format!(
|
||||
"ppc-edit-activities-{}.txt",
|
||||
chrono::Utc::now().timestamp()
|
||||
));
|
||||
std::fs::write(&tmp_file, tmp_file_content)
|
||||
.map_err(|e| mize_err!("Failed to write temp file: {}", e))?;
|
||||
|
||||
loop {
|
||||
// open $EDITOR blocking
|
||||
crate::cli_spawn(&mut mize, format!("nvim {}", tmp_file.display()))?;
|
||||
|
||||
let new_file_content = std::fs::read_to_string(&tmp_file)
|
||||
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
|
||||
|
||||
let mut input = String::new();
|
||||
|
||||
let old_activities = get_activities(&mut mize, con)?;
|
||||
if let Err(err) =
|
||||
parse_activities_from_text(&mut mize, con, new_file_content, old_activities)
|
||||
{
|
||||
out!("error parsing the activities from the file: {:?}", err);
|
||||
out!("press ENTER to open the file again");
|
||||
|
||||
// wait for user input
|
||||
// so that they can se the err msg, before running nvim again
|
||||
std::io::stdin().read_line(&mut input)?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// delete tmpfile
|
||||
let _ = std::fs::remove_file(&tmp_file);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_activities(_mize: &mut Mize, con: &DbConnection) -> MizeResult<Vec<Activity>> {
|
||||
let (exit_tx, exit_rx) = std::sync::oneshot::channel::<Vec<Activity>>();
|
||||
con.subscription_builder()
|
||||
.on_applied(|ctx| {
|
||||
exit_tx.send(ctx.db.activities().iter().collect()).unwrap();
|
||||
})
|
||||
.add_query(|q| q.from.activities())
|
||||
.subscribe();
|
||||
let activities = exit_rx.recv().unwrap();
|
||||
Ok(activities)
|
||||
}
|
||||
|
||||
fn parse_activities_from_text(
|
||||
mize: &mut Mize,
|
||||
con: &DbConnection,
|
||||
text: String,
|
||||
old_activities: Vec<Activity>,
|
||||
) -> MizeResult<()> {
|
||||
let mut activities_to_delete = old_activities.clone();
|
||||
let mut wait_revievers = Vec::new();
|
||||
for line in text.lines() {
|
||||
if line == "" {
|
||||
continue;
|
||||
}
|
||||
|
||||
// split
|
||||
let mut parts = line.split(" ");
|
||||
let id: u64 = parts.next().ok_or(mize_err!(".."))?.parse()?;
|
||||
let name = parts.next().ok_or(mize_err!(".."))?.to_string();
|
||||
|
||||
// remove from to-delete list
|
||||
if let Some(pos) = activities_to_delete.iter().position(|ac| ac.id == id) {
|
||||
activities_to_delete.remove(pos);
|
||||
}
|
||||
|
||||
// wait rx
|
||||
let (wait_tx, wait_rx) = oneshot::channel::<()>();
|
||||
wait_revievers.push(wait_rx);
|
||||
|
||||
// update or create activity
|
||||
if let Some(old_activity) = old_activities.iter().find(|ac| ac.id == id) {
|
||||
con.reducers.update_activity_then(
|
||||
Activity {
|
||||
id,
|
||||
name,
|
||||
task: old_activity.task,
|
||||
parents_ids: old_activity.parents_ids.clone(),
|
||||
},
|
||||
move |_, _| {
|
||||
let _ = wait_tx.send(());
|
||||
},
|
||||
)?;
|
||||
} else {
|
||||
con.reducers.update_activity_then(
|
||||
Activity {
|
||||
id: 0,
|
||||
name,
|
||||
task: None,
|
||||
parents_ids: Vec::new(),
|
||||
},
|
||||
move |_, _| {
|
||||
let _ = wait_tx.send(());
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// remove deleted activities
|
||||
for activity in activities_to_delete {
|
||||
let (wait_tx, wait_rx) = oneshot::channel::<()>();
|
||||
wait_revievers.push(wait_rx);
|
||||
con.reducers.remove_activity_then(activity, move |_, _| {
|
||||
let _ = wait_tx.send(());
|
||||
})?;
|
||||
}
|
||||
|
||||
// wait for db stuff to finish
|
||||
for wait_rx in wait_revievers {
|
||||
wait_rx.recv().unwrap();
|
||||
}
|
||||
let mut wait_revievers = Vec::new();
|
||||
|
||||
// check for declared parents
|
||||
let new_activities = get_activities(mize, con)?;
|
||||
out!("new_activities: {:?}", new_activities);
|
||||
for line in text.lines() {
|
||||
if line == "" {
|
||||
continue;
|
||||
}
|
||||
|
||||
// split
|
||||
let mut parts = line.split(" ");
|
||||
let _id: u64 = parts.next().ok_or(mize_err!(".."))?.parse()?;
|
||||
let name = parts.next().ok_or(mize_err!(".."))?.to_string();
|
||||
|
||||
// parse parents
|
||||
let seperator = parts.next();
|
||||
if seperator.is_some() && seperator.unwrap() == "-" {
|
||||
let mut parents_ids = Vec::new();
|
||||
for parent_name in parts {
|
||||
// find id from old_parts by this name
|
||||
let res_from_old = old_activities.iter().find(|ac| ac.name == parent_name);
|
||||
let res_from_new = new_activities.iter().find(|ac| ac.name == parent_name);
|
||||
let parent_activity = res_from_old.or(res_from_new).ok_or(mize_err!(
|
||||
"activity '{name}' declares parent '{parent_name}'"
|
||||
))?;
|
||||
parents_ids.push(parent_activity.id);
|
||||
}
|
||||
let child_activity = new_activities
|
||||
.iter()
|
||||
.find(|ac| ac.name == name)
|
||||
.ok_or(mize_err!(".."))?;
|
||||
let (wait_tx, wait_rx) = oneshot::channel::<()>();
|
||||
wait_revievers.push(wait_rx);
|
||||
con.reducers.update_activity_then(
|
||||
Activity {
|
||||
parents_ids,
|
||||
..child_activity.to_owned()
|
||||
},
|
||||
move |_, _| {
|
||||
let _ = wait_tx.send(());
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// wait for db stuff to finish
|
||||
for wait_rx in wait_revievers {
|
||||
wait_rx.recv().unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn part_timetrack_export(mize: &mut Mize) -> MizeResult<()> {
|
||||
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
|
||||
|
||||
cli.set_command("sci");
|
||||
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::<crate::SpacetimePart>("spacetime")?;
|
||||
let today = Local::now().format("%Y-%m-%d").to_string();
|
||||
let mut date = matches.get_one::<String>("date").unwrap_or(&today);
|
||||
let all = matches.get_flag("all");
|
||||
if all {
|
||||
out!("exporting all not implemented rn");
|
||||
let (tx, rx) = oneshot::channel();
|
||||
spacetime.con().procedures.first_timetrack_date_then("c2vi".to_string(), |_, res| {tx.send(res);});
|
||||
let res = rx.recv()???;
|
||||
let dates = dates_until_today(&res)?;
|
||||
for date in dates {
|
||||
out!("date: {}", date);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
spacetime.con().procedures.export_timetrack_then("c2vi".to_string(), date.to_owned(), |_, res| {tx.send(res);});
|
||||
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)))?;
|
||||
//file.write_all(res.as_bytes())?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
out!("date: {}", date);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
spacetime.con().procedures.export_timetrack_then("c2vi".to_string(), date.to_owned(), |_, res| {let _ = tx.send(res);});
|
||||
let res = rx.recv()???;
|
||||
let daily_notes_path = mize.get_config("timetrack.dailyNotesPath")?.value_string()?;
|
||||
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(())
|
||||
}
|
||||
|
||||
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> {
|
||||
use chrono::NaiveDate;
|
||||
let mut current = NaiveDate::parse_from_str(start, "%Y-%m-%d")?;
|
||||
let today = Local::now().date_naive();
|
||||
|
||||
let mut dates = Vec::new();
|
||||
|
||||
while current <= today {
|
||||
dates.push(current.format("%Y-%m-%d").to_string());
|
||||
current += TimeDelta::days(1);
|
||||
}
|
||||
|
||||
Ok(dates)
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
use std::sync::oneshot::Sender;
|
||||
|
||||
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
|
||||
|
||||
use crate::auth::client::ClientAuth;
|
||||
use generated::spacetime::DbConnection;
|
||||
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
pub struct SpacetimePart {
|
||||
mize: Mize,
|
||||
con: Option<DbConnection>,
|
||||
}
|
||||
|
||||
impl MizePartCreate for SpacetimePart {}
|
||||
impl MizePart for SpacetimePart {
|
||||
fn name(&self) -> &'static str {
|
||||
"spacetime"
|
||||
}
|
||||
}
|
||||
|
||||
impl SpacetimePart {
|
||||
pub fn con_mut(&mut self) -> &mut DbConnection {
|
||||
self.con.as_mut().unwrap()
|
||||
}
|
||||
pub fn con(&self) -> &DbConnection {
|
||||
self.con.as_ref().unwrap()
|
||||
}
|
||||
// does not work, because i can't use the QueryBuilder type in my code.... it's private
|
||||
/*
|
||||
pub fn get<T>(
|
||||
&self,
|
||||
on_applied: impl FnOnce(&generated::spacetime::SubscriptionEventContext, Sender<T>)+ Send + 'static,
|
||||
query: impl FnOnce(QueryBuilder)+ Send + 'static
|
||||
) -> MizeResult<T> {
|
||||
let (done_tx, done_rx) = std::sync::oneshot::channel::<T>();
|
||||
use spacetimedb_sdk::DbContext;
|
||||
self.con().subscription_builder().on_applied(move |ctx| {
|
||||
on_applied(ctx, done_tx);
|
||||
}).add_query(build);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
pub fn spacetime(mize: &mut Mize) -> MizeResult<()> {
|
||||
let auth = mize.get_part_native::<ClientAuth>("client_auth")?;
|
||||
let db_url = mize.get_config("spacetime.url")?.value_string()?;
|
||||
|
||||
let con: Option<DbConnection>;
|
||||
|
||||
#[cfg(feature = "target-os")] {
|
||||
con = Some(DbConnection::builder()
|
||||
.with_token(Some(auth.id_token()?))
|
||||
.with_database_name("mize")
|
||||
.with_uri(db_url)
|
||||
//.on_connect(callback)
|
||||
//.on_disconnect(callback)
|
||||
//.on_connect_error(callback)
|
||||
//.with_compression(...)
|
||||
.build()?);
|
||||
con.as_ref().unwrap().run_threaded();
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-js")] {
|
||||
let mut mize = mize.clone();
|
||||
con = None;
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let id_token = match auth.id_token() {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
mize.report_err(e.into());
|
||||
return;
|
||||
}
|
||||
};
|
||||
let con = match DbConnection::builder()
|
||||
.with_token(Some(id_token))
|
||||
.with_database_name("mize")
|
||||
.with_uri(db_url)
|
||||
//.on_connect(callback)
|
||||
//.on_disconnect(callback)
|
||||
//.on_connect_error(callback)
|
||||
//.with_compression(...)
|
||||
.build().await {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
mize.report_err(e.into());
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut spacetime = match mize.get_part_native::<SpacetimePart>("spacetime") {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
mize.report_err(e.into());
|
||||
return;
|
||||
}
|
||||
};
|
||||
con.run_async();
|
||||
spacetime.con = Some(con);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
mize.register_part(Box::new(SpacetimePart {
|
||||
mize: mize.clone(),
|
||||
con: con,
|
||||
}))
|
||||
}
|
||||
@@ -1,538 +0,0 @@
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkStringify from "remark-stringify";
|
||||
import { unified } from "unified";
|
||||
import { visit } from "unist-util-visit";
|
||||
import { Root, Content, Heading, List, ListItem, Text } from "mdast";
|
||||
import remarkFrontmatter from "remark-frontmatter";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import wikiLinkPlugin from "@flowershow/remark-wiki-link";
|
||||
import { TFile } from "obsidian";
|
||||
|
||||
const OTASK_PROP_NAMES = ["short", "outcome", "priority"];
|
||||
const ACTION_PROP_NAMES = [];
|
||||
|
||||
const REMARK = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkFrontmatter)
|
||||
.use(remarkGfm)
|
||||
.use(remarkStringify, { bullet: "-" });
|
||||
|
||||
export function task_obsidian_otask_parse(mize: any) {
|
||||
const obsidian_plugin = mize.get_part_native("obsidian");
|
||||
|
||||
obsidian_plugin.addCommand({
|
||||
id: "full-parse",
|
||||
name: "Parse all otask files into the db",
|
||||
callback: async () => {
|
||||
await full_parse();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function full_parse() {
|
||||
console.log("full parse.......................... yay");
|
||||
let otasks = [];
|
||||
OTASK_PARSE_QUEUE = [];
|
||||
OTASK_LIST = [];
|
||||
|
||||
const root_file = app.vault.getAbstractFileByPath("task/my-projects.md");
|
||||
|
||||
const content = await app.vault.read(root_file);
|
||||
const list = await getListUnderHeading(content, "root");
|
||||
|
||||
for (const entry of list) {
|
||||
queue_otasks_from_list_item(entry, []);
|
||||
}
|
||||
|
||||
// go through the queue of otaskParseTasks
|
||||
// using a queue instead of recursive calls, to make it more debuggable...
|
||||
do {
|
||||
const task = OTASK_PARSE_QUEUE.shift();
|
||||
dbg("task", task);
|
||||
if (task.file) {
|
||||
const otask = await otasks_from_file(
|
||||
task.file,
|
||||
task.otask_path,
|
||||
task.priority,
|
||||
);
|
||||
dbg("otask", otask);
|
||||
if (otask) {
|
||||
OTASK_LIST.push(otask);
|
||||
}
|
||||
} else {
|
||||
const otask = await otasks_from_list_item(
|
||||
task.listItem,
|
||||
task.otask_path,
|
||||
task.priority,
|
||||
);
|
||||
dbg("otask", otask);
|
||||
if (otask) {
|
||||
OTASK_LIST.push(otask);
|
||||
}
|
||||
}
|
||||
} while (OTASK_PARSE_QUEUE.length > 0);
|
||||
|
||||
console.log("otasks::", OTASK_LIST);
|
||||
|
||||
render_sub0_file(OTASK_LIST);
|
||||
}
|
||||
|
||||
let OTASK_PARSE_QUEUE: any = [];
|
||||
let OTASK_LIST = [];
|
||||
let DBG = false;
|
||||
|
||||
function dbg() {
|
||||
if (DBG) {
|
||||
console.log(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
function queue_otasks_from_list_item(listItem, otask_path, priority = 0) {
|
||||
OTASK_PARSE_QUEUE.push({
|
||||
listItem,
|
||||
otask_path,
|
||||
priority,
|
||||
});
|
||||
}
|
||||
|
||||
function queue_otasks_from_file(file, otask_path, priority = 0) {
|
||||
OTASK_PARSE_QUEUE.push({
|
||||
file,
|
||||
otask_path,
|
||||
priority,
|
||||
});
|
||||
}
|
||||
|
||||
// parsing helper functions
|
||||
|
||||
async function render_sub0_file(otasks) {
|
||||
const sub0_file = app.vault.getAbstractFileByPath("private/sub0-tasks.md");
|
||||
if (!(sub0_file instanceof TFile)) {
|
||||
console.error(
|
||||
"SystemC2: sub0_file not found:",
|
||||
"private/sub0-tasks.md",
|
||||
sub0_file,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
//render sub0
|
||||
const sub0_otasks = otasks.filter(
|
||||
(otask) => otask.priority == 0 && !otask.is_file,
|
||||
);
|
||||
console.log("sub0 otasks...", sub0_otasks);
|
||||
|
||||
let rendered_otasks_string = "";
|
||||
|
||||
for (const otask of sub0_otasks) {
|
||||
const path_part = otask.path.length > 0 ? otask.path.join(": ") + ": " : "";
|
||||
|
||||
let content_part = otask.content ? otask.content + "\n" : "";
|
||||
content_part = content_part
|
||||
.split("\n")
|
||||
.map((line) => "\t" + line)
|
||||
.join("\n"); // add a \t in front of every otask.content line
|
||||
|
||||
rendered_otasks_string += `- ${path_part}${otask.longName}\n${content_part}\n`;
|
||||
}
|
||||
//console.log("newContent...", rendered_otasks_string)
|
||||
|
||||
await replaceHeading(sub0_file, "sub0", rendered_otasks_string);
|
||||
|
||||
//render sub-1
|
||||
}
|
||||
|
||||
async function replaceHeading(
|
||||
file: TFile,
|
||||
heading: string,
|
||||
newContent: string,
|
||||
) {
|
||||
const data = await this.app.vault.read(file);
|
||||
|
||||
// Parse the Markdown into an AST
|
||||
const tree = REMARK.parse(data) as Root;
|
||||
|
||||
// Split new content into AST nodes
|
||||
const newContentAst = REMARK.parse(newContent) as Root;
|
||||
|
||||
const children = tree.children;
|
||||
const newChildren: Content[] = [];
|
||||
let insideTarget = false;
|
||||
let targetLevel = 0;
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const node = children[i];
|
||||
|
||||
if (node.type === "heading") {
|
||||
const headingNode = node as Heading;
|
||||
const text = headingNode.children
|
||||
.filter((c) => c.type === "text")
|
||||
.map((c: any) => c.value)
|
||||
.join("");
|
||||
|
||||
// If we encounter our target heading
|
||||
if (text.trim() === heading) {
|
||||
insideTarget = true;
|
||||
targetLevel = headingNode.depth;
|
||||
newChildren.push(node); // Keep the heading itself
|
||||
// Insert new content right after
|
||||
newChildren.push(...newContentAst.children);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we’re currently inside target section and see a heading of same or higher level
|
||||
if (insideTarget && headingNode.depth <= targetLevel) {
|
||||
insideTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we’re not inside the target section, keep the node
|
||||
if (!insideTarget) {
|
||||
newChildren.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
const newTree: Root = { ...tree, children: newChildren };
|
||||
|
||||
// Convert AST back to Markdown
|
||||
const newMarkdown = await REMARK.stringify(newTree);
|
||||
|
||||
await this.app.vault.modify(file, newMarkdown);
|
||||
}
|
||||
|
||||
async function otasks_from_list_item(listItem, otask_path, priority = 0) {
|
||||
try {
|
||||
// the main otask this listItem is about
|
||||
let otask = {
|
||||
description: [],
|
||||
actions: [],
|
||||
path: [],
|
||||
priority,
|
||||
};
|
||||
dbg("SystemC2: otasks_from_list_item", listItem, otask_path);
|
||||
|
||||
if (!listItem.children[0].type === "paragraph") {
|
||||
dbg(
|
||||
"listItem[0] of a otask item is not a paragraph... the otask:",
|
||||
listItem,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
let longName = nodeToString(listItem.children[0]);
|
||||
|
||||
dbg("longName:", longName);
|
||||
|
||||
// check for [[anotherotask]], call otasks_from_file, return those
|
||||
const match = longName.match(/\[\[([^\]]+)\]\]/);
|
||||
if (match) {
|
||||
const file = app.metadataCache.getFirstLinkpathDest(match[1], "");
|
||||
if (file) {
|
||||
const metadata = app.metadataCache.getFileCache(file);
|
||||
|
||||
if (metadata?.frontmatter?.tags?.includes("t/otask")) {
|
||||
queue_otasks_from_file(file, otask_path);
|
||||
return null;
|
||||
} else {
|
||||
dbg(
|
||||
"SystemC2: listItem with Link to '",
|
||||
match[1],
|
||||
"', which is not an otask",
|
||||
longName,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check for [x] and remove from name
|
||||
if (longName.startsWith("[ ") || longName.startsWith("[x")) {
|
||||
longName = longName.slice(4);
|
||||
}
|
||||
|
||||
//check for "ot:" and remove from name
|
||||
if (longName.startsWith("ot:")) {
|
||||
longName = longName.slice(4);
|
||||
}
|
||||
|
||||
otask.longName = longName;
|
||||
otask.short = longName;
|
||||
otask.path = otask_path;
|
||||
let new_otask_path = [...otask_path, otask.short];
|
||||
|
||||
// go through sub list items
|
||||
// - if it's a prop for the otask... handle that
|
||||
// - if it's an "ac:" handle that
|
||||
// - if it's "ot: " handle as another otask -> recursive call
|
||||
// - if it's "[ ]" handle as another otask -> recursive call
|
||||
// - if it's a link to a otask file... handle as another otask -> call otasks_from_file()
|
||||
// - else add it as description item list to otask
|
||||
if (!listItem.children || listItem.children.length < 2) {
|
||||
return otask;
|
||||
}
|
||||
|
||||
// add content as text to otask
|
||||
if (otask.longName == "hosting updates") {
|
||||
console.log("listItem... ", listItem.children[1]);
|
||||
let hii = REMARK.stringify(listItem.children[1]);
|
||||
console.log("text...", hii);
|
||||
}
|
||||
let content_text = REMARK.stringify(listItem.children[1]);
|
||||
otask.content = content_text;
|
||||
|
||||
for (const subListItem of listItem.children[1].children) {
|
||||
let text = nodeToString(subListItem.children[0]);
|
||||
|
||||
// check prop
|
||||
if (check_otask_prop_from_list_item(subListItem, otask)) {
|
||||
new_otask_path = [...otask_path, otask.short]; // have to redo it here, in case short changed from aprop
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// remove "[ ]"
|
||||
if (text.startsWith("[ ") || text.startsWith("[x")) {
|
||||
text = text.slice(4);
|
||||
}
|
||||
|
||||
// check ac
|
||||
if (text.startsWith("ac:")) {
|
||||
otask.actions = [
|
||||
...otask.actions,
|
||||
action_from_list_item(subListItem, new_otask_path, otask.priority),
|
||||
];
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// check ot
|
||||
if (text.startsWith("ot:")) {
|
||||
queue_otasks_from_list_item(
|
||||
subListItem,
|
||||
new_otask_path,
|
||||
otask.priority,
|
||||
);
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// check [ ]
|
||||
if (text.startsWith("[ ] ") || text.startsWith("[x] ")) {
|
||||
queue_otasks_from_list_item(
|
||||
subListItem,
|
||||
new_otask_path,
|
||||
otask.priority,
|
||||
);
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// check link to otask file
|
||||
const match = text.match(/\[\[([^\]]+)\]\]/);
|
||||
if (match) {
|
||||
const file = app.metadataCache.getFirstLinkpathDest(match[1], "");
|
||||
if (file) {
|
||||
const metadata = app.metadataCache.getFileCache(file);
|
||||
|
||||
if (metadata?.frontmatter?.tags?.includes("t/otask")) {
|
||||
queue_otasks_from_file(file, new_otask_path, otask.priority);
|
||||
continue;
|
||||
} else {
|
||||
dbg(
|
||||
"SystemC2: listItem with Link to '",
|
||||
match[1],
|
||||
"', which is not an otask",
|
||||
longName,
|
||||
);
|
||||
}
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
}
|
||||
|
||||
// add as description
|
||||
const desc = REMARK.stringify(subListItem);
|
||||
otask.description.push(desc);
|
||||
}
|
||||
|
||||
return otask;
|
||||
} catch (error) {
|
||||
console.log("failed to parse list item:", listItem);
|
||||
}
|
||||
}
|
||||
|
||||
async function otasks_from_file(file: TFile, otask_path, priority = 0) {
|
||||
try {
|
||||
dbg("SystemC2: otasks_from_file", file, otask_path);
|
||||
|
||||
// the main otask this file is about
|
||||
let otask = {
|
||||
description: [],
|
||||
actions: [],
|
||||
path: [],
|
||||
priority,
|
||||
is_file: true,
|
||||
};
|
||||
|
||||
otask.longName = file.basename;
|
||||
otask.short = otask.longName;
|
||||
otask.path = otask_path;
|
||||
let new_otask_path = [...otask_path, otask.short];
|
||||
|
||||
const content = await app.vault.read(file);
|
||||
|
||||
// parse listItems at the top... for props
|
||||
const topListItems = getListItemsBeforeFirstHeading(content);
|
||||
for (const topListItem of topListItems) {
|
||||
const text = nodeToString(topListItem);
|
||||
|
||||
// check for prop
|
||||
if (check_otask_prop_from_list_item(topListItem, otask)) {
|
||||
new_otask_path = [...otask_path, otask.short];
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
}
|
||||
|
||||
// parse all the sub sections
|
||||
const SUB_COUNT = 10;
|
||||
for (let sub_number = 0; sub_number < SUB_COUNT; sub_number++) {
|
||||
let listItems = await getListUnderHeading(content, "sub" + sub_number);
|
||||
|
||||
// "sub" without a number should have priority 5
|
||||
if (sub_number == 5) {
|
||||
listItems = listItems.concat(await getListUnderHeading(content, "sub"));
|
||||
}
|
||||
|
||||
//dbg(`sub section ${sub_number} has items:`, listItems)
|
||||
|
||||
for (const listItem of listItems) {
|
||||
// check if listItem is an action...
|
||||
let text = nodeToString(listItem.children[0]);
|
||||
if (
|
||||
text.startsWith("ac:") ||
|
||||
text.startsWith("[ ] ac:") ||
|
||||
text.startsWith("[x] ac:")
|
||||
) {
|
||||
const action = action_from_list_item(listItem, otask_path);
|
||||
otask.actions.push(action);
|
||||
}
|
||||
|
||||
queue_otasks_from_list_item(
|
||||
listItem,
|
||||
new_otask_path,
|
||||
otask.priority + sub_number,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return otask;
|
||||
} catch (error) {
|
||||
console.log("failed to parse file: ", file.path);
|
||||
}
|
||||
}
|
||||
|
||||
function action_from_list_item(listItem, otask_path) {
|
||||
dbg("SystemC2: action_from_list_item", listItem, otask_path);
|
||||
let action = {};
|
||||
let text = nodeToString(listItem.children[0]).slice(4);
|
||||
|
||||
if (text.startsWith("[")) {
|
||||
text = text.slice(4);
|
||||
}
|
||||
|
||||
action.name = text;
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function check_otask_prop_from_list_item(listItem, otask) {
|
||||
let text = nodeToString(listItem.children[0]);
|
||||
|
||||
for (const prop_name of OTASK_PROP_NAMES) {
|
||||
if (text.startsWith(prop_name + ":")) {
|
||||
const value = text.slice(prop_name.length + 2);
|
||||
|
||||
if (prop_name == "priority") {
|
||||
// parse priority as int
|
||||
otask[prop_name] = parseInt(value);
|
||||
} else if (prop_name == "outcome") {
|
||||
// add sub items to it
|
||||
let fullText = REMARK.stringify(listItem).slice(prop_name.length + 4);
|
||||
otask[prop_name] = fullText;
|
||||
} else {
|
||||
otask[prop_name] = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract plain text from a node
|
||||
function nodeToString(node: any): string {
|
||||
let result = "";
|
||||
visit(node, "text", (textNode: Text) => {
|
||||
result += textNode.value;
|
||||
});
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
async function getListUnderHeading(
|
||||
markdown: string,
|
||||
headingText: string,
|
||||
): string[] {
|
||||
const tree = REMARK.parse(markdown);
|
||||
//dbg("tree", tree)
|
||||
|
||||
let capture = false;
|
||||
let items: string[] = [];
|
||||
|
||||
for (let i = 0; i < tree.children.length; i++) {
|
||||
const node: Content = tree.children[i];
|
||||
|
||||
// Detect the target heading
|
||||
if (
|
||||
node.type === "heading" &&
|
||||
nodeToString(node).toLowerCase() === headingText.toLowerCase()
|
||||
) {
|
||||
capture = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stop if another heading of the same or higher level comes
|
||||
if (capture && node.type === "heading") {
|
||||
break;
|
||||
}
|
||||
|
||||
// Collect list items if capturing
|
||||
if (capture && node.type === "list") {
|
||||
for (const li of (node as List).children) {
|
||||
if (li.type === "listItem") {
|
||||
items.push(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function getListItemsBeforeFirstHeading(markdown: string) {
|
||||
const tree = REMARK.parse(markdown);
|
||||
|
||||
let items = [];
|
||||
|
||||
for (const node of tree.children) {
|
||||
if (node.type === "heading") {
|
||||
// stop at first heading
|
||||
break;
|
||||
}
|
||||
if (node.type === "list") {
|
||||
for (const li of (node as List).children) {
|
||||
if (li.type === "listItem") {
|
||||
items.push(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
Reference in New Issue
Block a user