recent mize changes

This commit is contained in:
Sebastian Moser
2026-09-16 21:32:53 +00:00
parent 05d410949c
commit d559159397
45 changed files with 11247 additions and 45 deletions

View File

@@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"]
clap = { version = "4.5.59", optional = true, features = ["string"] }
flume = "0.12.0"
futures = "0.3.32"
mize = { path = "../mize/", default-features = false, features = [] }
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 }
@@ -19,7 +19,7 @@ tokio = { version = "1.49.0", default-features = false, features = ["time"] }
reqwest = { version = "0.13.2", features = ["blocking", "json"] }
serde = "1.0.228"
serde_json = "1.0.149"
rusqlite = { version = "0.31", features = ["bundled"], optional = true }
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" }
@@ -39,7 +39,10 @@ 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"
[features]
default = ["target-os", "desktop", "server", "client"]
#default = ["target-js", "target-obsidian"] # to get rust analyzer to analyzer js only parts
@@ -51,3 +54,4 @@ desktop = ["dioxus/desktop"]
client = []
server = []
clap = ["dep:clap"]
serenity = ["dep:serenity"]

View File

@@ -108,7 +108,7 @@ impl ClientAuth {
pub fn refresh(&mut self) -> MizeResult<()> {
#[cfg(feature = "target-js")] {
return Err(mize_err!("refresh is not supported on wasm"));
console_log!("refresh is not implemented yet on wasm");
}
#[cfg(feature = "target-os")] {
@@ -321,6 +321,7 @@ pub fn part_client_auth(mize: &mut Mize) -> MizeResult<()> {
// 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 {

View File

@@ -52,7 +52,7 @@ impl C2vi {
}
}
pub fn c2vi(mize: &mut Mize) -> MizeResult<()> {
pub fn part_c2vi(mize: &mut Mize) -> MizeResult<()> {
#[cfg(feature = "desktop")]
if mize
.get_config("c2vi.dash.enable")
@@ -62,6 +62,22 @@ pub fn c2vi(mize: &mut Mize) -> MizeResult<()> {
{
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(())
}

167
marts/src/discord/mod.rs Normal file
View File

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

View File

@@ -15,7 +15,6 @@ pub mod c2vi {
pub mod part;
pub mod dashboard;
}
#[cfg(feature = "target-os")]
pub use c2vi::part::*;
#[cfg(feature = "target-obsidian")]
@@ -36,6 +35,13 @@ pub mod ppc;
pub mod timetrack;
#[cfg(feature = "target-obsidian")]
pub mod matrix;
#[cfg(feature = "target-obsidian")]
pub use matrix::part_matrix;
pub mod sc2;
//#[cfg(feature = "target-os")]
//pub mod js;
//#[cfg(feature = "target-os")]

247
marts/src/matrix/mod.rs Normal file
View File

@@ -0,0 +1,247 @@
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());
};
}

269
marts/src/matrix/ui.rs Normal file
View File

@@ -0,0 +1,269 @@
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());
};
}

View File

@@ -65,7 +65,7 @@ impl ObsidianPart {
Reflect::set(&obsidian_cmd, &"id".into(), &"login".into()).mres()?;
Reflect::set(&obsidian_cmd, &"name".into(), &"Login".into()).mres()?;
// callback: () => run_login()
// 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()) {
@@ -127,6 +127,7 @@ fn obsidian_mize_main(mize: &mut Mize, plugin: JsValue, app: JsValue) -> MizeRes
crate::part_obsidian(mize, plugin, app)?;
crate::part_client_auth(mize)?;
crate::spacetime(mize)?;
crate::part_matrix(mize)?;
Ok(())
}

7
marts/src/sc2/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
use mize::*;
pub fn part_sc2(mize: &mut Mize) -> MizeResult<()> {
Ok(())
}