c2vi dash working

This commit is contained in:
Sebastian Moser
2026-05-06 12:48:20 +02:00
parent 562b1abe9c
commit c9ea571744
23 changed files with 1174 additions and 1991 deletions

1773
Cargo.lock generated

File diff suppressed because it is too large Load Diff

141
flake.nix
View File

@@ -35,7 +35,7 @@ let
];
wasmCrane = (crane.mkLib pkgs).overrideToolchain wasmToolchain;
osToolchain = fenix.packages.${system}.nightly.toolchain;
osToolchain = fenix.packages.${system}.latest.toolchain;
osCrane = (crane.mkLib pkgs).overrideToolchain osToolchain;
@@ -120,11 +120,67 @@ in {
});
};
};
default = osCrane.buildPackage {
src = "${self}";
cargoExtraArgs = "--bin mize --features target-os";
fileSetForCrate =
crate:
pkgs.lib.fileset.toSource {
root = ./.;
fileset = pkgs.lib.fileset.unions [
./Cargo.toml
./Cargo.lock
(craneLib.fileset.commonCargoSources ./packages/marts)
(craneLib.fileset.commonCargoSources ./packages/mize)
(craneLib.fileset.commonCargoSources ./packages/ppc/spacetimedb)
(craneLib.fileset.commonCargoSources ./packages/mme)
(craneLib.fileset.commonCargoSources ./packages/vic)
(craneLib.fileset.commonCargoSources ./packages/ac_mize_macros)
(craneLib.fileset.commonCargoSources crate)
];
};
ppc = osCrane.buildPackage {
src = fileSetForCrate ./packages/ppc;
cargoExtraArgs = "-p ppc";
nativeBuildInputs = [
pkgs.pkg-config
pkgs.gobject-introspection
pkgs.gobject-introspection.dev
];
buildInputs = with pkgs; [
git # needed to build spacetimedb as it needs to get it's git hash for setting it's version
at-spi2-atk
atkmm
cairo
gdk-pixbuf
glib
gtk3
harfbuzz
librsvg
libsoup_3
pango
webkitgtk_4_1
openssl
wasm-bindgen-cli
lld_20
xdotool
];
LD_LIBRARY_PATH = with pkgs; pkgs.lib.makeLibraryPath [
xdotool
webkitgtk_4_1
pango
openssl
at-spi2-atk
atkmm
cairo
gdk-pixbuf
glib
gtk3
harfbuzz
librsvg
libsoup_3
];
};
#SPACETIMEDB_NIX_BUILD_GIT_COMMIT =
wasm = wasmCrane.buildPackage {
src = "${self}";
@@ -213,6 +269,81 @@ in {
#default = (mizeLib.buildMizeForSystem system).modules.mize.devShell;
buildShell = pkgs.mkShell {
MIZE_BUILD_CONFIG = let
settingsFormat = pkgs.formats.toml { };
in settingsFormat.generate "mize-build-config.toml" {
config = {
namespace = "mize.buildtime.ns";
module_url = "c2vi.dev";
};
};
shellHook = ''
mize_project_root=$(${pkgs.lib.getExe pkgs.git} rev-parse --show-toplevel)
export MIZE_CONFIG_FILES=$mize_project_root/mize_config.toml
'';
nativeBuildInputs = [
pkgs.wasm-pack
pkgs.pkg-config
pkgs.gobject-introspection
pkgs.gobject-introspection.dev
# thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes
(pkgs.buildWasmBindgenCli rec {
src = pkgs.fetchCrate {
pname = "wasm-bindgen-cli";
version = "0.2.114";
hash = "sha256-xrCym+rFY6EUQFWyWl6OPA+LtftpUAE5pIaElAIVqW0=";
};
cargoDeps = pkgs.rustPlatform.fetchCargoVendor {
inherit src;
inherit (src) pname version;
hash = "sha256-Z8+dUXPQq7S+Q7DWNr2Y9d8GMuEdSnq00quUR0wDNPM=";
};
})
(fenix.packages.${system}.combine [
fenix.packages.${system}.latest.toolchain
fenix.packages.${system}.targets.wasm32-unknown-unknown.latest.toolchain
])
];
buildInputs = with pkgs; [
at-spi2-atk
atkmm
cairo
gdk-pixbuf
glib
gtk3
harfbuzz
librsvg
libsoup_3
pango
webkitgtk_4_1
openssl
wasm-bindgen-cli
lld_20
xdotool
];
LD_LIBRARY_PATH = with pkgs; pkgs.lib.makeLibraryPath [
xdotool
webkitgtk_4_1
pango
openssl
at-spi2-atk
atkmm
cairo
gdk-pixbuf
glib
gtk3
harfbuzz
librsvg
libsoup_3
];
};
default = pkgs.mkShell {
MIZE_BUILD_CONFIG = let

21
md
View File

@@ -2,7 +2,7 @@
set -e
MIZE_SRC="/home/me/work/mize" # hardcoded for now
RSYNC_EXCLUDES="--exclude=.git --exclude=node_modules --exclude=target --exclude=mize_config.toml --exclude=rust_dist --exclude=deno_dist --exclude=dist --exclude=result --exclude=pkg"
RSYNC_EXCLUDES="--exclude=node_modules --exclude=target --exclude=mize_config.toml --exclude=rust_dist --exclude=deno_dist --exclude=dist --exclude=result --exclude=pkg"
function main {
if [[ "$1" == "dlu" ]]; then
@@ -23,13 +23,22 @@ function deploy_lush {
echo "c2vi: Deploying to lush..."
# Copy files to lush
rsync -r -u -v --delete --mkpath $RSYNC_EXCLUDES $MIZE_SRC/* lush:here/mize
rsync -r -u -v -a --delete --include=".git/" --include=".git/**" --mkpath $RSYNC_EXCLUDES $MIZE_SRC lush:here
# compile
ssh lush nix develop /home/me/here/mize --impure -v -L -c sh -c "cargo build --release --manifest-path /home/me/here/mize/packages/ppc/Cargo.toml"
# restart service
ssh lush "sudo systemctl restart ppc"
ssh lush << EOF
if ! tmux has-session -t ppc-deploy 2>/dev/null; then
echo "Created new tmux session...."
tmux new-session -d -s ppc-deploy "nix develop git+file:///home/me/here/mize#buildShell"
tmux send-keys -t ppc-deploy "cargo build --featuers desktop --manifest-path /home/me/here/mize/packages/ppc/Cargo.toml" Enter
tmux send-keys -t ppc-deploy "systemctl --user restart ppc" Enter
else
echo "using existing session..."
tmux send-keys -t ppc-deploy "cargo build --features desktop --manifest-path /home/me/here/mize/packages/ppc/Cargo.toml" Enter
tmux send-keys -t ppc-deploy "systemctl --user restart ppc" Enter
fi
EOF
}
function deploy_main_website {

View File

@@ -11,7 +11,7 @@ futures = "0.3.32"
mize = { path = "../mize/", default-features = false }
tokio = "1.49.0"
# https://github.com/rscarson/rustyscript/pull/415
rustyscript = { git = 'https://github.com/rscarson/rustyscript', rev = 'e39529eac2c5d37cfcbb7d7d63da7264d7a5afdb', optional = true }
#rustyscript = { git = 'https://github.com/rscarson/rustyscript', rev = 'e39529eac2c5d37cfcbb7d7d63da7264d7a5afdb', optional = true }
#deno_core = { version = "0.380.1", optional = true }
chrono = "0.4.44"
reqwest = { version = "0.13.2", features = ["blocking", "json"] }
@@ -22,7 +22,7 @@ dioxus = { version = "0.7.1", optional = true }
[features]
default = ["target-os", "desktop", "server"]
target-os = [ "clap", "mize/target-os", "rustyscript", "rusqlite", "dioxus" ]
target-os = [ "clap", "mize/target-os", "rusqlite", "dioxus" ]
desktop = ["dioxus/desktop"]
server = []
target-js = [ "mize/target-js" ]

View File

@@ -51,7 +51,7 @@ pub fn c2vi(mize: &mut Mize) -> MizeResult<()> {
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);
//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))?;

View File

@@ -51,14 +51,11 @@ pub fn gather_config(mize: &mut Mize) -> MizeResult<()> {
println!("reading config file: {config_file_path}");
let content = std::fs::read_to_string(config_file_path)?;
let mut data = ItemData::from_toml(content.as_str())?;
println!("config data: {data}");
for path in data.get_paths_recursive()? {
let mut conf_name = path.replace("/", ".");
if conf_name.starts_with(".") {
conf_name.remove(0);
}
println!("adding config {conf_name} from config file {config_file_path}");
println!("path: {:?}", path);
let val = data.get_path(path.split("/").filter(|s| s != &"").collect::<Vec<&str>>())?;
match config_opts.get_mut(&conf_name) {
Some(opt) => {

View File

@@ -869,7 +869,7 @@ impl Mize {
// get the cached value
if let Some(val) = opt.val.clone() {
println!("get_config: {name} has cached value: {val}");
//println!("get_config: {name} has cached value: {val}");
return Ok(val);
}

View File

@@ -47,7 +47,7 @@ path = "lib.rs"
name = "ppc"
[features]
default = ["target-os", "server", "client"]
default = ["target-os", "server", "client", "desktop"]
target-os = [ "clap", "mize/target-os", "marts/target-os", "tracing", "webbrowser", "base64", "serde_json", "serde" ]
target-obsidian = [ "js-sys", "wasm-bindgen", "mize/target-js", "marts/target-js", "base64", "serde_json", "serde", "openidconnect", "dioxus", "dioxus-web", "web-sys", "web-sys/Document", "dioxus/web" ]
server = [ "axum", "axum-extra", "openidconnect", "serde", "serde_json", "reqwest", "rand", "url", "time", "tokio", "dioxus/ssr", "dioxus/server", "dioxus/fullstack", "dioxus/router", "dioxus-ssr", "html-escape", "axum-oidc", "tower", "tower-sessions", "marts/server", "target-os" ]

View File

@@ -3,17 +3,19 @@ use base64::{
alphabet::STANDARD,
engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig},
};
use dioxus::{fullstack::ClientResponse, html::ol};
use mize::{Mize, MizeError, MizePart, MizeResult, item::ItemData, mize_err, mize_part};
use openidconnect::{
AuthorizationCode, ClientId, CsrfToken, DeviceAuthorizationResponse,
AccessToken, AuthorizationCode, ClientId, CsrfToken, DeviceAuthorizationResponse,
EmptyExtraDeviceAuthorizationFields, IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge,
PkceCodeVerifier, RedirectUrl, RefreshToken, Scope,
PkceCodeVerifier, RedirectUrl, RefreshToken, Scope, TokenResponse,
core::{CoreAuthenticationFlow, CoreClient, CoreProviderMetadata},
};
use serde::{Deserialize, Serialize};
use std::{
io::{self, Write},
path::PathBuf,
thread,
};
// console_log macro
@@ -45,7 +47,7 @@ pub struct ClientAuth {
secrets: Option<ClientSecrets>,
}
#[derive(Default, Serialize, Deserialize)]
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct ClientSecrets {
id_token: String,
access_token: String,
@@ -79,23 +81,64 @@ impl ClientAuth {
}
Ok(&self.secrets.as_ref().unwrap().refresh_token)
}
pub fn refresh(&mut self) -> MizeResult<()> {
let secrets = self.secrets.clone().ok_or(mize_err!(
"no client secrets, meaning the refresh thread can't use the refresh token"
))?;
self.mize.spawn_async_blocking(
"auth.refresh_flow",
refresh_flow(self.mize.clone(), secrets),
)?;
Ok(())
}
pub fn start_refresh_thread(&mut self) -> MizeResult<()> {
let mut mize_clone = self.mize.clone();
self.mize.spawn_background("auth.refresh", move || {
loop {
thread::sleep(Duration::from_hours(23));
let mut auth = mize_clone.get_part_native::<ClientAuth>("auth")?;
auth.refresh()?;
drop(auth);
}
})?;
Ok(())
}
}
pub fn client_auth(mize: &mut Mize) -> MizeResult<()> {
let mize_clone = mize.clone();
let secrets = match read_secrets(mize) {
let mut secrets = match read_secrets(mize) {
Ok(secrets) => Some(secrets),
Err(_) => None,
Err(err) => {
println!("err reading secrets: {:?}", err);
None
}
};
#[cfg(feature = "target-os")]
{
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?;
cli.subcommand(Command::new("login"), |_, mut mize| {
let secrets = mize.spawn_async_blocking("client_auth", auth_flow(mize_clone))?;
write_secrets(&mut mize, &secrets)?;
let mut auth = mize.get_part_native::<ClientAuth>("client_auth")?;
println!("secrets: {:?}", auth.secrets);
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) => {
println!("err refreshing secrets: {:?}", err);
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone))?
}
}
} else {
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone))?
};
write_secrets(&mut mize, &secrets)?;
auth.secrets = Some(secrets);
Ok(())
});
@@ -104,6 +147,16 @@ pub fn client_auth(mize: &mut Mize) -> MizeResult<()> {
#[cfg(feature = "target-obsidian")]
{}
// check if login token is stil valid refresh if not
/*
if secrets.is_some() {
secrets = Some(mize.spawn_async_blocking(
"client_auth",
refresh_flow(mize.clone(), secrets.clone().unwrap()),
)?);
}
*/
mize.register_part(Box::new(ClientAuth {
secrets,
mize: mize.clone(),
@@ -143,13 +196,79 @@ struct Combined<'a> {
state: &'a str,
}
use openidconnect::DeviceAuthorizationUrl;
use openidconnect::reqwest::Client as HttpClient;
use openidconnect::{AuthUrl, DeviceAuthorizationUrl};
use std::time::Duration;
use tokio::time::sleep;
async fn refresh_flow(mut mize: Mize, old_secrets: ClientSecrets) -> MizeResult<ClientSecrets> {
println!("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::AdditionalProviderMetadata;
use openidconnect::ProviderMetadata;
use openidconnect::core::{
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,
CoreDeviceAuthorizationResponse, CoreGrantType, CoreJsonWebKey, CoreJsonWebKeyType,
CoreJsonWebKeyUse, CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm,
CoreJwsSigningAlgorithm, CoreResponseMode, CoreResponseType, CoreSubjectIdentifierType,
};
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?;
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(),
});
}
async fn auth_flow(mut mize: Mize) -> MizeResult<ClientSecrets> {
// ===== CONFIG =====
let issuer = mize.get_config("auth.client.issuer")?.value_string()?;
let client_id = mize.get_config("auth.client.client_id")?.value_string()?;

View File

@@ -0,0 +1,60 @@
// 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 {}

View File

@@ -1,53 +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 struct Event {
pub start: __sdk::Timestamp,
pub end: __sdk::Timestamp,
pub activity: Activity,
}
impl __sdk::InModule for Event {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `Event`.
///
/// Provides typed access to columns for query building.
pub struct EventCols {
pub start: __sdk::__query_builder::Col<Event, __sdk::Timestamp>,
pub end: __sdk::__query_builder::Col<Event, __sdk::Timestamp>,
pub activity: __sdk::__query_builder::Col<Event, Activity>,
}
impl __sdk::__query_builder::HasCols for Event {
type Cols = EventCols;
fn cols(table_name: &'static str) -> Self::Cols {
EventCols {
start: __sdk::__query_builder::Col::new(table_name, "start"),
end: __sdk::__query_builder::Col::new(table_name, "end"),
activity: __sdk::__query_builder::Col::new(table_name, "activity"),
}
}
}
/// Indexed column accessor struct for the table `Event`.
///
/// Provides typed access to indexed columns for query building.
pub struct EventIxCols {}
impl __sdk::__query_builder::HasIxCols for Event {
type IxCols = EventIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
EventIxCols {}
}
}
impl __sdk::__query_builder::CanBeLookupTable for Event {}

View File

@@ -1,112 +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 super::event_type::Event;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `events`.
///
/// Obtain a handle from the [`EventsTableAccess::events`] method on [`super::RemoteTables`],
/// like `ctx.db.events()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.events().on_insert(...)`.
pub struct EventsTableHandle<'ctx> {
imp: __sdk::TableHandle<Event>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `events`.
///
/// Implemented for [`super::RemoteTables`].
pub trait EventsTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`EventsTableHandle`], which mediates access to the table `events`.
fn events(&self) -> EventsTableHandle<'_>;
}
impl EventsTableAccess for super::RemoteTables {
fn events(&self) -> EventsTableHandle<'_> {
EventsTableHandle {
imp: self.imp.get_table::<Event>("events"),
ctx: std::marker::PhantomData,
}
}
}
pub struct EventsInsertCallbackId(__sdk::CallbackId);
pub struct EventsDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for EventsTableHandle<'ctx> {
type Row = Event;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = Event> + '_ {
self.imp.iter()
}
type InsertCallbackId = EventsInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> EventsInsertCallbackId {
EventsInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: EventsInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = EventsDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> EventsDeleteCallbackId {
EventsDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: EventsDeleteCallbackId) {
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::<Event>("events");
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<Event>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<Event>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `Event`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait eventsQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `Event`.
fn events(&self) -> __sdk::__query_builder::Table<Event>;
}
impl eventsQueryTableAccess for __sdk::QueryTableAccessor {
fn events(&self) -> __sdk::__query_builder::Table<Event> {
__sdk::__query_builder::Table::new("events")
}
}

View File

@@ -0,0 +1,60 @@
// 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,
}
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>,
}
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"),
}
}
}
/// 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 {}

View File

@@ -0,0 +1,160 @@
// 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")
}
}

View File

@@ -6,11 +6,13 @@
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
pub mod action_type;
pub mod activities_table;
pub mod activity_type;
pub mod add_activity_reducer;
pub mod event_type;
pub mod events_table;
pub mod log_event_type;
pub mod log_events_table;
pub mod next_action_reducer;
pub mod profile_type;
pub mod profiles_table;
pub mod remove_activity_reducer;
@@ -20,12 +22,15 @@ pub mod task_part_type;
pub mod task_type;
pub mod tasks_table;
pub mod update_activity_reducer;
pub mod update_upcoming_reducer;
pub use action_type::Action;
pub use activities_table::*;
pub use activity_type::Activity;
pub use add_activity_reducer::add_activity;
pub use event_type::Event;
pub use events_table::*;
pub use log_event_type::LogEvent;
pub use log_events_table::*;
pub use next_action_reducer::next_action;
pub use profile_type::Profile;
pub use profiles_table::*;
pub use remove_activity_reducer::remove_activity;
@@ -35,6 +40,7 @@ pub use task_part_type::TaskPart;
pub use task_type::Task;
pub use tasks_table::*;
pub use update_activity_reducer::update_activity;
pub use update_upcoming_reducer::update_upcoming;
#[derive(Clone, PartialEq, Debug)]
@@ -45,9 +51,11 @@ pub use update_activity_reducer::update_activity;
pub enum Reducer {
AddActivity { name: String },
NextAction,
RemoveActivity { activity: Activity },
SetActivityName { id: u64, name: String },
UpdateActivity { activity: Activity },
UpdateUpcoming { upcoming: Vec<Action> },
}
impl __sdk::InModule for Reducer {
@@ -58,9 +66,11 @@ impl __sdk::Reducer for Reducer {
fn reducer_name(&self) -> &'static str {
match self {
Reducer::AddActivity { .. } => "add_activity",
Reducer::NextAction => "next_action",
Reducer::RemoveActivity { .. } => "remove_activity",
Reducer::SetActivityName { .. } => "set_activity_name",
Reducer::UpdateActivity { .. } => "update_activity",
Reducer::UpdateUpcoming { .. } => "update_upcoming",
_ => unreachable!(),
}
}
@@ -70,6 +80,7 @@ impl __sdk::Reducer for Reducer {
Reducer::AddActivity { name } => {
__sats::bsatn::to_vec(&add_activity_reducer::AddActivityArgs { name: name.clone() })
}
Reducer::NextAction => __sats::bsatn::to_vec(&next_action_reducer::NextActionArgs {}),
Reducer::RemoveActivity { activity } => {
__sats::bsatn::to_vec(&remove_activity_reducer::RemoveActivityArgs {
activity: activity.clone(),
@@ -86,6 +97,11 @@ impl __sdk::Reducer for Reducer {
activity: activity.clone(),
})
}
Reducer::UpdateUpcoming { upcoming } => {
__sats::bsatn::to_vec(&update_upcoming_reducer::UpdateUpcomingArgs {
upcoming: upcoming.clone(),
})
}
_ => unreachable!(),
}
}
@@ -96,7 +112,7 @@ impl __sdk::Reducer for Reducer {
#[doc(hidden)]
pub struct DbUpdate {
activities: __sdk::TableUpdate<Activity>,
events: __sdk::TableUpdate<Event>,
log_events: __sdk::TableUpdate<LogEvent>,
profiles: __sdk::TableUpdate<Profile>,
task_part: __sdk::TableUpdate<TaskPart>,
tasks: __sdk::TableUpdate<Task>,
@@ -111,9 +127,9 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate {
"activities" => db_update
.activities
.append(activities_table::parse_table_update(table_update)?),
"events" => db_update
.events
.append(events_table::parse_table_update(table_update)?),
"log_events" => db_update
.log_events
.append(log_events_table::parse_table_update(table_update)?),
"profiles" => db_update
.profiles
.append(profiles_table::parse_table_update(table_update)?),
@@ -152,7 +168,9 @@ impl __sdk::DbUpdate for DbUpdate {
diff.activities = cache
.apply_diff_to_table::<Activity>("activities", &self.activities)
.with_updates_by_pk(|row| &row.id);
diff.events = cache.apply_diff_to_table::<Event>("events", &self.events);
diff.log_events = cache
.apply_diff_to_table::<LogEvent>("log_events", &self.log_events)
.with_updates_by_pk(|row| &row.id);
diff.profiles = cache
.apply_diff_to_table::<Profile>("profiles", &self.profiles)
.with_updates_by_pk(|row| &row.identity);
@@ -172,8 +190,8 @@ impl __sdk::DbUpdate for DbUpdate {
"activities" => db_update
.activities
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"events" => db_update
.events
"log_events" => db_update
.log_events
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"profiles" => db_update
.profiles
@@ -200,8 +218,8 @@ impl __sdk::DbUpdate for DbUpdate {
"activities" => db_update
.activities
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"events" => db_update
.events
"log_events" => db_update
.log_events
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"profiles" => db_update
.profiles
@@ -228,7 +246,7 @@ impl __sdk::DbUpdate for DbUpdate {
#[doc(hidden)]
pub struct AppliedDiff<'r> {
activities: __sdk::TableAppliedDiff<'r, Activity>,
events: __sdk::TableAppliedDiff<'r, Event>,
log_events: __sdk::TableAppliedDiff<'r, LogEvent>,
profiles: __sdk::TableAppliedDiff<'r, Profile>,
task_part: __sdk::TableAppliedDiff<'r, TaskPart>,
tasks: __sdk::TableAppliedDiff<'r, Task>,
@@ -246,7 +264,7 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> {
callbacks: &mut __sdk::DbCallbacks<RemoteModule>,
) {
callbacks.invoke_table_row_callbacks::<Activity>("activities", &self.activities, event);
callbacks.invoke_table_row_callbacks::<Event>("events", &self.events, event);
callbacks.invoke_table_row_callbacks::<LogEvent>("log_events", &self.log_events, event);
callbacks.invoke_table_row_callbacks::<Profile>("profiles", &self.profiles, event);
callbacks.invoke_table_row_callbacks::<TaskPart>("task_part", &self.task_part, event);
callbacks.invoke_table_row_callbacks::<Task>("tasks", &self.tasks, event);
@@ -911,11 +929,11 @@ impl __sdk::SpacetimeModule for RemoteModule {
fn register_tables(client_cache: &mut __sdk::ClientCache<Self>) {
activities_table::register_table(client_cache);
events_table::register_table(client_cache);
log_events_table::register_table(client_cache);
profiles_table::register_table(client_cache);
task_part_table::register_table(client_cache);
tasks_table::register_table(client_cache);
}
const ALL_TABLE_NAMES: &'static [&'static str] =
&["activities", "events", "profiles", "task_part", "tasks"];
&["activities", "log_events", "profiles", "task_part", "tasks"];
}

View File

@@ -0,0 +1,62 @@
// 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 NextActionArgs {}
impl From<NextActionArgs> for super::Reducer {
fn from(args: NextActionArgs) -> Self {
Self::NextAction
}
}
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) -> __sdk::Result<()> {
self.next_action_then(|_, _| {})
}
/// 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,
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,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp
.invoke_reducer_with_callback(NextActionArgs {}, callback)
}
}

View File

@@ -2,7 +2,7 @@
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::activity_type::Activity;
use super::action_type::Action;
use super::task_part_type::TaskPart;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};

View File

@@ -4,13 +4,15 @@
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::activity_type::Activity;
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 current_activity: Activity,
pub start_of_current: __sdk::Timestamp,
pub current: Action,
pub upcoming: Vec<Action>,
}
impl __sdk::InModule for TaskPart {
@@ -22,7 +24,9 @@ impl __sdk::InModule for TaskPart {
/// Provides typed access to columns for query building.
pub struct TaskPartCols {
pub identity: __sdk::__query_builder::Col<TaskPart, __sdk::Identity>,
pub current_activity: __sdk::__query_builder::Col<TaskPart, Activity>,
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 {
@@ -30,7 +34,9 @@ impl __sdk::__query_builder::HasCols for TaskPart {
fn cols(table_name: &'static str) -> Self::Cols {
TaskPartCols {
identity: __sdk::__query_builder::Col::new(table_name, "identity"),
current_activity: __sdk::__query_builder::Col::new(table_name, "current_activity"),
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"),
}
}
}

View File

@@ -0,0 +1,70 @@
// 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)
}
}

View File

@@ -39,14 +39,28 @@ fn os_main(mize: &mut Mize) -> MizeResult<()> {
#[cfg(feature = "server")]
ppc::server(mize)?;
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?;
#[cfg(feature = "client")]
#[cfg(feature = "desktop")]
cli.subcommand(Command::new("gui"), |_, _| {
ppc::ui::launch_desktop_app();
Ok(())
});
{
cli.subcommand(Command::new("gui"), |_, _| {
ppc::ui::launch_desktop_app();
Ok(())
});
cli.subcommand(Command::new("c2vi-dash"), |_, mut mize| {
// refresh the credentials every 23h
// as this is basically running as a daemon
use ppc::auth::client::ClientAuth;
let mut auth = mize.get_part_native::<ClientAuth>("auth")?;
auth.refresh()?;
auth.start_refresh_thread()?;
drop(auth);
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?;
ppc::ui::launch_c2vi_dashboard(&mut mize);
Ok(())
});
}
cli.with_cmd(|_, cmd| {
Ok(cmd

View File

@@ -1,5 +1,18 @@
// Minimal cross-platform UI component
use dioxus::prelude::*;
use mize::Mize;
use crate::{
generated::{
Action, ActivitiesTableAccess, Activity, DbConnection, TaskPart, TaskPartTableAccess,
activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess,
update_activity, update_upcoming,
},
spacetime::SpacetimePart,
};
use spacetimedb_sdk::{
DbContext, Error, Event, Identity, Status, SubscriptionHandle, Table, TableWithPrimaryKey,
credentials,
};
pub fn hello_world() -> Element {
rsx! {
@@ -12,6 +25,126 @@ pub fn hello_world() -> Element {
}
}
pub fn c2vi_dash_main() -> Element {
let mut current = use_signal_sync(|| "".to_string());
use_effect(move || {
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(|q| q.from.task_part())
.subscribe();
con.db
.task_part()
.on_update(move |_, old: &TaskPart, new: &TaskPart| {
current.set(new.current.name.clone());
});
});
rsx! {
div {
style: "text-align: center; padding: 2rem; font-size: 50px;",
p {
"{current}"
}
}
}
}
pub fn launch_desktop_app() {
dioxus::launch(hello_world);
}
#[cfg(feature = "desktop")]
pub fn launch_c2vi_dashboard(mize: &mut Mize) {
use dioxus::desktop::{Config, WindowBuilder, muda::Menu};
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("PPC"))
})
.launch(c2vi_dash_main);
}
use dioxus::prelude::*;
/*
pub fn use_spacetime_row<T>() -> {
let mut mize = use_context::<Mize>();
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap();
let con = spacetime.con();
let data = use_signal(|| None:: <T>);
let a = con.db.task_part();
let table = con.db.
use_effect(cx, (), {});
}
pub fn use_spacetime_users() -> Signal<Vec<User>> {
let mut mize = use_context::<Mize>();
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap();
let con = spacetime.con();
let users = use_signal(|| Vec::<User>::new());
use_effect(cx, (), {
let users = users.clone();
let conn = ctx.conn.clone();
move |_| {
conn.on_connect(move |db_ctx| {
// initial subscription
db_ctx.subscription_builder()
.on_applied({
let users = users.clone();
move |ctx| {
let all: Vec<_> =
ctx.db.user().iter().cloned().collect();
users.set(all);
}
})
.subscribe(["SELECT * FROM user"]);
// incremental updates
db_ctx.db.user().on_insert({
let users = users.clone();
move |_, row| {
users.with_mut(|v| v.push(row.clone()));
}
});
db_ctx.db.user().on_delete({
let users = users.clone();
move |_, row| {
users.with_mut(|v| {
v.retain(|u| u.id != row.id);
});
}
});
db_ctx.db.user().on_update({
let users = users.clone();
move |_, old, new| {
users.with_mut(|v| {
if let Some(u) = v.iter_mut().find(|u| u.id == old.id) {
*u = new.clone();
}
});
}
});
});
async move {}
}
});
users
}
*/

View File

@@ -1,5 +1,10 @@
use std::task;
use log::*;
use spacetimedb::{reducer, table, Identity, ReducerContext, Table, Timestamp};
use spacetimedb::{
reducer, table, view, Deserialize, Identity, ReducerContext, Serialize, SpacetimeType, Table,
Timestamp, ViewContext,
};
#[table(accessor = profiles, public)]
pub struct Profile {
@@ -13,7 +18,9 @@ pub struct Profile {
pub struct TaskPart {
#[primary_key]
identity: Identity,
current_activity: Activity,
start_of_current: Timestamp,
current: Action,
upcoming: Vec<Action>,
}
#[table(accessor = activities, public)]
@@ -36,14 +43,26 @@ pub struct Task {
parent: Option<u64>,
}
#[table(accessor = events, public)]
pub struct Event {
start: Timestamp,
end: Timestamp,
activity: Activity,
#[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>,
}
// TODO....
#[table(accessor = log_events, public)]
pub struct LogEvent {
#[primary_key]
#[auto_inc]
id: u64,
start: Timestamp,
end: Timestamp,
action: Action,
}
#[reducer(init)]
pub fn init(_ctx: &ReducerContext) {
@@ -106,6 +125,57 @@ pub fn set_activity_name(ctx: &ReducerContext, id: u64, name: String) {
}
}
#[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) -> 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 action = task_part.upcoming.remove(0);
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: 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,
});
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 {

View File

@@ -1,6 +1,6 @@
use std::{io::Read, sync::oneshot, thread, time::Duration};
use clap::{ArgMatches, Command};
use clap::{Arg, ArgAction, ArgMatches, Command};
use mize::{Mize, MizeError, MizePart, MizePartGuard, MizeResult, mize_err, mize_part};
use spacetimedb_sdk::{
DbContext, Error, Event, Identity, Status, SubscriptionHandle, Table, TableWithPrimaryKey,
@@ -9,8 +9,9 @@ use spacetimedb_sdk::{
use crate::{
generated::{
ActivitiesTableAccess, Activity, DbConnection, activitiesQueryTableAccess, remove_activity,
update_activity,
Action, ActivitiesTableAccess, Activity, DbConnection, TaskPartTableAccess,
activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess,
update_activity, update_upcoming,
},
spacetime::SpacetimePart,
};
@@ -24,6 +25,23 @@ pub struct TimeTrack {
pub fn timetrack(mize: &mut Mize) -> MizeResult<()> {
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?;
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"), 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,
@@ -43,9 +61,58 @@ impl MizePart for TimeTrack {
}
}
fn command_next_action(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
let con = spacetime.con();
let (wait_tx, waiter_rx) = std::sync::oneshot::channel::<()>();
con.reducers.next_action_then(|_, _| {
wait_tx.send(()).unwrap();
})?;
waiter_rx.recv()?;
Ok(())
}
fn command_current_action(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
let action = get_current_action(&mut mize, spacetime.con())?;
println!("{}", 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)
}
fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut 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() {
@@ -60,6 +127,121 @@ fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
Ok(())
}
fn command_list_actions(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
let actions = get_actions(spacetime.con())?;
for action in actions {
println!("{}", action.name)
}
Ok(())
}
fn command_edit_action_list(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
// maybe append a template to the action list
if matches.get_flag("defaultTemplate") {
println!("Appending template...");
return Ok(());
}
// get list of actions
let mut actions = get_actions(spacetime.con())?;
// write to tmp file
let mut 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
std::process::Command::new("nvim")
.arg(&tmp_file)
.status()
.map_err(|e| mize_err!("Failed to run nvim: {}", e))?;
// 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(actions, 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(mut old_actions: Vec<Action>, 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();
}
fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;