diff --git a/Cargo.toml b/Cargo.toml index c3768c9..d363a87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,42 +1,22 @@ -[package] -name = "SimpleAIEditor" -version = "0.1.0" -edition = "2021" - -[[bin]] -name = "SimpleAIEditor" -path = "src/main.rs" - -[build-dependencies] -copy_dir = { version = "0.1.3" } - -[dependencies] -sai_macros = { path = "extern/macros" } -async-recursion = { version = "1.1.1" } -chrono = { version = "0.4.38", features = ["serde"] } -sai_backend = { path = "extern/backend" } -dioxus = { git = "https://github.com/DioxusLabs/dioxus.git" } -futures = "0.3.31" -reqwest = { version = "0.12.9", features = ["json"] } -serde = { version = "1.0.215", features = ["derive"] } -serde_json = { version = "1.0.133" } -dirs = { version = "6.0.0" } -toml = { version = "0.8.19" } -include_dir = { version = "0.7.3" } -anyhow = { version = "1.0.95" } -colored = { version = "3.0.0" } -regex = { version = "1.11.1" } -derive-new = { version = "0.7.0" } -derive_builder = { version = "0.20.2" } -thiserror = { version = "2.0.11"} -tokio = { version = "1.43.0", features = ["sync"]} - -[features] -default = [] -desktop = ["dioxus/desktop"] -web = ["dioxus/web", "dioxus/router"] -mobile = ["dioxus/mobile"] -dev = ["dioxus/devtools", "dioxus/liveview"] +[workspace] +resolver = "2" +members = [ + "packages/backend", + "packages/frontend", + "packages/macros", + "packages/platforms/desktop", + "packages/platforms/web" +] + +[workspace.package] +version = "0.6.3" + +[workspace.dependencies] +simple-ai-backend = { path = "packages/backend" } +simple-ai-frontend = { path = "packages/frontend" } +simple-ai-macros = { path = "packages/macros" } + +dioxus = { version = "0.6.3" } [profile] diff --git a/Makefile b/Makefile index 0efa016..6e8d247 100644 --- a/Makefile +++ b/Makefile @@ -1,35 +1,24 @@ -CARGO := cargo DIOXUS := dx -APP_NAME = SimpleAIEditor -TAURI_TARGET = target/release/bundle -WEB_TARGET = target/wasm32-unknown-unknown/release -DESKTOP_TARGET = target/release - -APP_PATH = $(DESKTOP_TARGET)/$(APP_NAME) - - all: build-web build-desktop build-web: @echo "Building for WebAssembly..." - $(CARGO) build --release --target wasm32-unknown-unknown --features "web" - + $(DIOXUS) build --release --package simple-ai-web --platform web build-desktop: @echo "Building for Desktop..." - $(CARGO) build --release --features "desktop" + $(DIOXUS) build --release --package simple-ai-desktop --platform desktop run-web: @echo "Running Web version..." - $(DIOXUS) serve --platform web --features "web" + $(DIOXUS) serve --package simple-ai-web --platform web -run-desktop: build-desktop +run-desktop: @echo "Running Desktop version..." - exec $(APP_PATH) + $(DIOXUS) serve --package simple-ai-desktop --platform desktop clean: @echo "Cleaning the project..." - $(CARGO) clean - rm -rf $(WEB_TARGET) + $(DIOXUS) clean .PHONY: all build-web build-desktop run-web run-desktop clean diff --git a/assets/themes/styles/cybr1/pages/editor/index.css b/assets/themes/styles/cybr1/pages/editor/index.css deleted file mode 100644 index 5abf11e..0000000 --- a/assets/themes/styles/cybr1/pages/editor/index.css +++ /dev/null @@ -1,40 +0,0 @@ -@import "../index.css"; - -main -{ - all : revert; - align-items : center; - display : flex; - flex-direction : row; - flex-wrap : nowrap; - height : 100%; - overflow : clip; - width : 100%; -} - -aside -{ - height : 100%; - margin : 0; - padding : 0; - width : 30%; -} - -h1 -{ - height : fit-content; - margin : 0; - padding : 0; - text-align : center; - width : 70%; -} - -section -{ - height: 100%; -} - -.Search -{ - height: 100%; -} \ No newline at end of file diff --git a/extern/backend/nodes/complex_bundled_node/815883006474706806af1b7768d3919fd65e95346e36534e1aa355e910521de1/0.0.2.bin b/extern/backend/nodes/complex_bundled_node/815883006474706806af1b7768d3919fd65e95346e36534e1aa355e910521de1/0.0.2.bin deleted file mode 100644 index 144a774..0000000 Binary files a/extern/backend/nodes/complex_bundled_node/815883006474706806af1b7768d3919fd65e95346e36534e1aa355e910521de1/0.0.2.bin and /dev/null differ diff --git a/extern/backend/nodes/test_delete_invalid_version/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/1.0.0.bin b/extern/backend/nodes/test_delete_invalid_version/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/1.0.0.bin deleted file mode 100644 index f577f04..0000000 Binary files a/extern/backend/nodes/test_delete_invalid_version/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/1.0.0.bin and /dev/null differ diff --git a/extern/backend/nodes/test_delete_invalid_version/meta.toml b/extern/backend/nodes/test_delete_invalid_version/meta.toml deleted file mode 100644 index 614c883..0000000 --- a/extern/backend/nodes/test_delete_invalid_version/meta.toml +++ /dev/null @@ -1,10 +0,0 @@ -name = "test_delete_invalid_version" -description = "A node with a single version" -author = "Author" -date = "2025-03-10T18:11:02.512933191Z" - -[[versions]] -version = "1.0.0" - -[versions.env] -deps = [] diff --git a/extern/backend/nodes/test_delete_version/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/2.0.0.bin b/extern/backend/nodes/test_delete_version/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/2.0.0.bin deleted file mode 100644 index 4751d17..0000000 Binary files a/extern/backend/nodes/test_delete_version/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/2.0.0.bin and /dev/null differ diff --git a/extern/backend/nodes/test_delete_version/meta.toml b/extern/backend/nodes/test_delete_version/meta.toml deleted file mode 100644 index f83aea5..0000000 --- a/extern/backend/nodes/test_delete_version/meta.toml +++ /dev/null @@ -1,10 +0,0 @@ -name = "test_delete_version" -description = "A node with multiple versions" -author = "Author" -date = "2025-03-10T18:11:02.512959856Z" - -[[versions]] -version = "2.0.0" - -[versions.env] -deps = [] diff --git a/extern/macros/Cargo.toml b/extern/macros/Cargo.toml deleted file mode 100644 index be7532d..0000000 --- a/extern/macros/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "sai_macros" -edition = "2021" - -[lib] -path = "src/lib.rs" -proc-macro = true - -[dependencies] -proc-macro2 = { version = "1" } -syn = { version = "2.0", features = ["full"] } -quote = { version = "1.0" } diff --git a/extern/macros/src/element.rs b/extern/macros/src/element.rs deleted file mode 100644 index 7b99cbb..0000000 --- a/extern/macros/src/element.rs +++ /dev/null @@ -1,178 +0,0 @@ -use proc_macro2::TokenStream; -use quote::quote; -use syn::{ - parse::{Parse, ParseStream, Result}, - parse2, - punctuated::Punctuated, - FnArg, ItemFn, LitStr, Pat, Token, -}; - -struct ElementAttributes { - emt_type: LitStr, -} - -impl Parse for ElementAttributes { - fn parse(input: ParseStream) -> Result { - Ok(ElementAttributes { - emt_type: input.parse()?, - }) - } -} - -fn get_function_parameter_names(inputs: &Punctuated) -> Vec { - inputs - .iter() - .filter_map(|arg| { - match arg { - // Match named arguments (e.g., `x: i32`) - FnArg::Typed(pat_type) => { - if let Pat::Ident(pat_ident) = &*pat_type.pat { - Some(pat_ident.ident.to_string()) - } else { - None - } - } - // Ignore self arguments (e.g., `self`, `&self`, `&mut self`) - FnArg::Receiver(_) => None, - } - }) - .collect() -} - -fn remove_function_parameter( - param_name: &str, - inputs: &mut Punctuated, -) -> Option { - let mut ret = None; - let mut new_inputs: Punctuated = Punctuated::new(); - for arg in inputs.iter() { - match arg { - FnArg::Typed(pat_type) => { - if let Pat::Ident(pat_ident) = *pat_type.pat.clone() { - if pat_ident.ident == param_name { - ret = Some(arg.clone()); - } else { - new_inputs.push(arg.clone()); - } - } - } - FnArg::Receiver(_) => {} - } - } - *inputs = new_inputs; - ret -} - -pub fn macro_impl(attr: TokenStream, item: TokenStream) -> TokenStream { - let out_attrs: ElementAttributes = parse2(attr).unwrap(); - let out_items: ItemFn = parse2(item).unwrap(); - - let fn_name = &out_items.sig.ident; - let fn_args = &out_items.sig.inputs; - let fn_body = &out_items.block; - - let emt_type = out_attrs.emt_type; - let emt_name = fn_name.to_string().to_lowercase(); - - let mut new_fn_args = fn_args.to_owned(); - - let removed_style = remove_function_parameter("style", &mut new_fn_args); - let removed_icons = remove_function_parameter("icons", &mut new_fn_args); - - let current_dir = quote! { - fn current_dir(theme_dir: &std::path::PathBuf) -> std::path::PathBuf { - theme_dir - .join(format!("{}s", #emt_type)) - .join(#emt_name) - } - }; - - let mut style = TokenStream::new(); - - if removed_style.is_some() { - style = quote! { - let style: String = { - use crate::assets::{ dirs::paths::*, files::get_file }; - - #current_dir - - let theme_dir = current_style(); - - let style_dir = current_dir(&theme_dir); - - let style_config = get_file(style_dir.join("config.css")) - .contents_utf8() - .unwrap(); - - let style_index = get_file(style_dir.join("index.css")) - .contents_utf8() - .unwrap(); - - let colors = get_file(theme_dir.join("colors.css")) - .contents_utf8() - .unwrap(); - - let config = get_file(theme_dir.join("config.css")) - .contents_utf8() - .unwrap(); - - let index = get_file(theme_dir.join("index.css")) - .contents_utf8() - .unwrap(); - - format!("{}\n{}\n{}\n{}\n{}", index, colors, config, style_config, style_index) - }; - }; - } - - let mut icons = TokenStream::new(); - - if removed_icons.is_some() { - icons = quote! { - type Icons = std::collections::HashMap; - - let icons: Icons = { - use crate::assets::{ dirs::{ get_dir, paths::* }, files::get_file }; - - #current_dir - - let theme_dir = current_icons(); - - let icons_dir = current_dir(&theme_dir); - - let mut map = Icons::new(); - - for file in get_dir(icons_dir).files() { - map.insert( file.path().file_stem().unwrap().to_str().unwrap().to_owned(), file.contents_utf8().unwrap().to_string() ); - } - - map - }; - }; - } - - let new_body = quote! { - #style - #icons - #fn_body - }; - - let render_name = { - let mut rn = TokenStream::new(); - if emt_type.value() == "page" { - rn = quote! { pub static NAME: &str = #emt_name; } - } - rn - }; - - let output = quote! { - #render_name - use dioxus::prelude::*; - #[component] - pub fn #fn_name(#new_fn_args) -> Element { - #new_body - } - }; - - output -} diff --git a/extern/backend/.gitignore b/packages/backend/.gitignore similarity index 100% rename from extern/backend/.gitignore rename to packages/backend/.gitignore diff --git a/extern/backend/Cargo.toml b/packages/backend/Cargo.toml similarity index 81% rename from extern/backend/Cargo.toml rename to packages/backend/Cargo.toml index 1fc0f13..46b9b58 100644 --- a/extern/backend/Cargo.toml +++ b/packages/backend/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "sai_backend" +name = "simple-ai-backend" +version = { workspace = true } edition = "2021" [features] default = [] desktop = [] -web = [] +web = ["uuid/js"] mobile = [] [dependencies] @@ -18,7 +19,7 @@ derive-new = { version = "0.7.0" } derive_builder = { version = "0.20.2" } anyhow = { version = "1.0.95" } sha2 = { version = "0.10.8" } -uuid = { version = "1.15.1", features = ["v4"] } +uuid = { version = "1.15.1", features = ["v4"]} bincode = { version = "1.1.3" } tokio = { version = "1.43.0", features = ["sync"] } fuzzy-matcher = "0.3.7" diff --git a/extern/backend/nodes/bundled_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/0.0.1.bin b/packages/backend/nodes/bundled_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/0.0.1.bin similarity index 100% rename from extern/backend/nodes/bundled_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/0.0.1.bin rename to packages/backend/nodes/bundled_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/0.0.1.bin diff --git a/packages/backend/nodes/bundled_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/node.bin b/packages/backend/nodes/bundled_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/node.bin new file mode 100644 index 0000000..8428a81 Binary files /dev/null and b/packages/backend/nodes/bundled_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/node.bin differ diff --git a/extern/backend/nodes/bundled_node/meta.toml b/packages/backend/nodes/bundled_node/meta.toml similarity index 100% rename from extern/backend/nodes/bundled_node/meta.toml rename to packages/backend/nodes/bundled_node/meta.toml diff --git a/extern/backend/nodes/complex_bundled_node/347d4e7d76b463a7b8415486415c698bf2c792ee6384f96eca96e1d45a2f3986/0.0.1.bin b/packages/backend/nodes/complex_bundled_node/347d4e7d76b463a7b8415486415c698bf2c792ee6384f96eca96e1d45a2f3986/0.0.1.bin similarity index 100% rename from extern/backend/nodes/complex_bundled_node/347d4e7d76b463a7b8415486415c698bf2c792ee6384f96eca96e1d45a2f3986/0.0.1.bin rename to packages/backend/nodes/complex_bundled_node/347d4e7d76b463a7b8415486415c698bf2c792ee6384f96eca96e1d45a2f3986/0.0.1.bin diff --git a/packages/backend/nodes/complex_bundled_node/347d4e7d76b463a7b8415486415c698bf2c792ee6384f96eca96e1d45a2f3986/node.bin b/packages/backend/nodes/complex_bundled_node/347d4e7d76b463a7b8415486415c698bf2c792ee6384f96eca96e1d45a2f3986/node.bin new file mode 100644 index 0000000..1848209 Binary files /dev/null and b/packages/backend/nodes/complex_bundled_node/347d4e7d76b463a7b8415486415c698bf2c792ee6384f96eca96e1d45a2f3986/node.bin differ diff --git a/extern/backend/nodes/complex_bundled_node/meta.toml b/packages/backend/nodes/complex_bundled_node/meta.toml similarity index 100% rename from extern/backend/nodes/complex_bundled_node/meta.toml rename to packages/backend/nodes/complex_bundled_node/meta.toml diff --git a/extern/backend/nodes/test_code_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/0.0.1.bin b/packages/backend/nodes/test_code_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/0.0.1.bin similarity index 100% rename from extern/backend/nodes/test_code_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/0.0.1.bin rename to packages/backend/nodes/test_code_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/0.0.1.bin diff --git a/packages/backend/nodes/test_code_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/node.bin b/packages/backend/nodes/test_code_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/node.bin new file mode 100644 index 0000000..c1be2f7 Binary files /dev/null and b/packages/backend/nodes/test_code_node/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/node.bin differ diff --git a/extern/backend/nodes/test_code_node/meta.toml b/packages/backend/nodes/test_code_node/meta.toml similarity index 100% rename from extern/backend/nodes/test_code_node/meta.toml rename to packages/backend/nodes/test_code_node/meta.toml diff --git a/extern/backend/src/lib.rs b/packages/backend/src/lib.rs similarity index 100% rename from extern/backend/src/lib.rs rename to packages/backend/src/lib.rs diff --git a/extern/backend/src/modules.rs b/packages/backend/src/modules.rs similarity index 100% rename from extern/backend/src/modules.rs rename to packages/backend/src/modules.rs diff --git a/extern/backend/src/modules/compiler.rs b/packages/backend/src/modules/compiler.rs similarity index 100% rename from extern/backend/src/modules/compiler.rs rename to packages/backend/src/modules/compiler.rs diff --git a/extern/backend/src/modules/compiler/model.rs b/packages/backend/src/modules/compiler/model.rs similarity index 100% rename from extern/backend/src/modules/compiler/model.rs rename to packages/backend/src/modules/compiler/model.rs diff --git a/extern/backend/src/modules/nms.rs b/packages/backend/src/modules/nms.rs similarity index 100% rename from extern/backend/src/modules/nms.rs rename to packages/backend/src/modules/nms.rs diff --git a/packages/backend/src/modules/nms/create.rs b/packages/backend/src/modules/nms/create.rs new file mode 100644 index 0000000..5d70ed5 --- /dev/null +++ b/packages/backend/src/modules/nms/create.rs @@ -0,0 +1,52 @@ +use crate::nms::check_name; +use crate::utils::prelude::{save::*, *}; +use anyhow::Result; +use std::fs::{create_dir, File}; +use std::io::Write; +use std::path::Path; + +// #[cfg(feature = "desktop")] +pub fn create_node(node: Node) -> Result<(), String> { + let name = node.name.clone(); + + if !check_name(name.clone()) { + return Err(format!( + "Node name {} is not allowed! Please only use letters, dashes and underscores.", + name + )); + } + if !Path::new("nodes/").exists() { + create_dir(Path::new("nodes/")).map_err(|e| e.to_string())?; + } + if Path::new("nodes/").join(name.clone()).exists() { + return Err(format!("A node named {} does already exist!", name)); + } + + create_dir(Path::new("nodes/").join(name.clone())).map_err(|e| e.to_string())?; + let meta: Metadata = node.clone().into(); + let meta_toml = toml::to_string(&meta).unwrap(); + let mut meta_file = File::create(Path::new("nodes/").join(name.clone()).join("meta.toml")) + .map_err(|e| e.to_string())?; + meta_file + .write_all(meta_toml.as_bytes()) + .map_err(|e| e.to_string())?; + + let env_hash = meta.impls[0].clone().1; + create_dir( + Path::new("nodes/") + .join(name.clone()) + .join(env_hash.clone()), + ) + .map_err(|e| e.to_string())?; + let node_bin = bincode::serialize(&SaveNode::from(node)).map_err(|e| e.to_string())?; + let mut node_file = File::create( + Path::new("nodes/") + .join(name) + .join(env_hash) + .join("node.bin"), + ) + .map_err(|e| e.to_string())?; + node_file.write_all(&node_bin).map_err(|e| e.to_string())?; + + Ok(()) +} diff --git a/extern/backend/src/modules/nms/delete.rs b/packages/backend/src/modules/nms/delete.rs similarity index 100% rename from extern/backend/src/modules/nms/delete.rs rename to packages/backend/src/modules/nms/delete.rs diff --git a/assets/themes/icons/con1/components/search/sample.svg b/packages/backend/src/modules/nms/modify.rs similarity index 100% rename from assets/themes/icons/con1/components/search/sample.svg rename to packages/backend/src/modules/nms/modify.rs diff --git a/extern/backend/src/modules/nms/query.rs b/packages/backend/src/modules/nms/query.rs similarity index 100% rename from extern/backend/src/modules/nms/query.rs rename to packages/backend/src/modules/nms/query.rs diff --git a/extern/backend/src/modules/nms/save.rs b/packages/backend/src/modules/nms/save.rs similarity index 100% rename from extern/backend/src/modules/nms/save.rs rename to packages/backend/src/modules/nms/save.rs diff --git a/extern/backend/src/modules/utils.rs b/packages/backend/src/modules/utils.rs similarity index 100% rename from extern/backend/src/modules/utils.rs rename to packages/backend/src/modules/utils.rs diff --git a/extern/backend/src/modules/utils/container.rs b/packages/backend/src/modules/utils/container.rs similarity index 100% rename from extern/backend/src/modules/utils/container.rs rename to packages/backend/src/modules/utils/container.rs diff --git a/extern/backend/src/modules/utils/context.rs b/packages/backend/src/modules/utils/context.rs similarity index 64% rename from extern/backend/src/modules/utils/context.rs rename to packages/backend/src/modules/utils/context.rs index 1af6ebf..9c3a5e5 100644 --- a/extern/backend/src/modules/utils/context.rs +++ b/packages/backend/src/modules/utils/context.rs @@ -2,7 +2,7 @@ use derive_new::new; use std::sync::{Arc, Weak}; use tokio::sync::Mutex; // -------------------- STRONG CONTEXT -------------------- // -#[derive(new, Clone)] +#[derive(new, Clone, Default)] pub struct StrongContext { pub context: Arc>, } @@ -11,6 +11,9 @@ impl StrongContext { pub fn downgrade(self) -> WeakContext { WeakContext::from(self) } + pub fn set(&mut self, context: Self) { + self.context = context.context; + } } impl PartialEq for StrongContext @@ -18,9 +21,13 @@ where T: PartialEq, { fn eq(&self, other: &Self) -> bool { - let self_data = self.context.blocking_lock(); - let other_data = other.context.blocking_lock(); - *self_data == *other_data + // TODO: try to make this reliable (with blocking_lock -> remove the error you'll get) + let self_data = self.context.try_lock(); + let other_data = other.context.try_lock(); + if self_data.is_err() || other_data.is_err() { + return true; + } + *self_data.unwrap() == *other_data.unwrap() } } @@ -40,11 +47,14 @@ impl From> for Option> { } // -------------------- WEAK CONTEXT -------------------- // -#[derive(new, Clone)] +#[derive(Clone, Default)] pub struct WeakContext { pub context: Weak>, } impl WeakContext { + pub fn new() -> Self { + Self::from(Weak::new()) + } pub fn upgrade(self) -> Option> { Option::>::from(self) } @@ -62,8 +72,13 @@ where false } } +impl From>> for WeakContext { + fn from(weak: Weak>) -> Self { + Self { context: weak } + } +} impl From> for WeakContext { - fn from(other: StrongContext) -> Self { - Self::new(Arc::downgrade(&other.context)) + fn from(strong: StrongContext) -> Self { + Self::from(Arc::downgrade(&strong.context)) } } diff --git a/extern/backend/src/modules/utils/date.rs b/packages/backend/src/modules/utils/date.rs similarity index 100% rename from extern/backend/src/modules/utils/date.rs rename to packages/backend/src/modules/utils/date.rs diff --git a/extern/backend/src/modules/utils/dtype.rs b/packages/backend/src/modules/utils/dtype.rs similarity index 100% rename from extern/backend/src/modules/utils/dtype.rs rename to packages/backend/src/modules/utils/dtype.rs diff --git a/extern/backend/src/modules/utils/environment.rs b/packages/backend/src/modules/utils/environment.rs similarity index 100% rename from extern/backend/src/modules/utils/environment.rs rename to packages/backend/src/modules/utils/environment.rs diff --git a/extern/backend/src/modules/utils/metadata.rs b/packages/backend/src/modules/utils/metadata.rs similarity index 100% rename from extern/backend/src/modules/utils/metadata.rs rename to packages/backend/src/modules/utils/metadata.rs diff --git a/extern/backend/src/modules/utils/node.rs b/packages/backend/src/modules/utils/node.rs similarity index 98% rename from extern/backend/src/modules/utils/node.rs rename to packages/backend/src/modules/utils/node.rs index f95bf43..3a8d05a 100644 --- a/extern/backend/src/modules/utils/node.rs +++ b/packages/backend/src/modules/utils/node.rs @@ -22,6 +22,8 @@ pub struct Node { pub author: String, pub compiled: Option, // or and bytes... pub date: Date, + #[builder(default)] + pub position: Option<(f64, f64)>, } impl Node { diff --git a/extern/backend/src/modules/utils/param.rs b/packages/backend/src/modules/utils/param.rs similarity index 100% rename from extern/backend/src/modules/utils/param.rs rename to packages/backend/src/modules/utils/param.rs diff --git a/extern/backend/src/modules/utils/query_filter.rs b/packages/backend/src/modules/utils/query_filter.rs similarity index 100% rename from extern/backend/src/modules/utils/query_filter.rs rename to packages/backend/src/modules/utils/query_filter.rs diff --git a/extern/backend/src/modules/utils/save_node.rs b/packages/backend/src/modules/utils/save_node.rs similarity index 100% rename from extern/backend/src/modules/utils/save_node.rs rename to packages/backend/src/modules/utils/save_node.rs diff --git a/extern/backend/src/modules/utils/save_param.rs b/packages/backend/src/modules/utils/save_param.rs similarity index 100% rename from extern/backend/src/modules/utils/save_param.rs rename to packages/backend/src/modules/utils/save_param.rs diff --git a/packages/backend/src/modules/utils/search.rs b/packages/backend/src/modules/utils/search.rs new file mode 100644 index 0000000..c498303 --- /dev/null +++ b/packages/backend/src/modules/utils/search.rs @@ -0,0 +1,27 @@ +use super::prelude::*; +use chrono::Utc; +/// This function searches through all available Nodes and returns a NodeContainer containing all Nodes available for the inferred environment. +pub fn search(query: String) -> Vec { + // TODO: implement search function here and add a node container parameter to infer the + // environment + // + + // TODO: remove this: + let mut c = Vec::::new(); + c.push( + NodeBuilder::default() + .name("test_code_node".to_string()) + .params(vec![]) + .kind(NodeKind::Code { + code: "fn main() { println!(\"Hello, world!\"); }".to_string(), + }) + .description("A simple code node".to_string()) + .author("Author".to_string()) + .compiled(None) + .environment(Environment { deps: vec![] }) + .date(Utc::now()) + .build() + .unwrap(), + ); + c +} diff --git a/extern/backend/tests.rs b/packages/backend/tests.rs similarity index 100% rename from extern/backend/tests.rs rename to packages/backend/tests.rs diff --git a/extern/backend/tests/create.rs b/packages/backend/tests/create.rs similarity index 100% rename from extern/backend/tests/create.rs rename to packages/backend/tests/create.rs diff --git a/extern/backend/tests/delete.rs b/packages/backend/tests/delete.rs similarity index 100% rename from extern/backend/tests/delete.rs rename to packages/backend/tests/delete.rs diff --git a/extern/macros/.gitignore b/packages/frontend/.gitignore similarity index 100% rename from extern/macros/.gitignore rename to packages/frontend/.gitignore diff --git a/packages/frontend/Cargo.toml b/packages/frontend/Cargo.toml new file mode 100644 index 0000000..7e5cf3a --- /dev/null +++ b/packages/frontend/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "simple-ai-frontend" +version = { workspace = true } +edition = "2021" + +[dependencies] +dioxus = { workspace = true, features = ["router"]} +simple-ai-backend = { workspace = true } +simple-ai-macros = { workspace = true } +async-recursion = { version = "1.1.1" } +futures = "0.3.31" +reqwest = { version = "0.12.9", features = ["json"] } +serde = { version = "1.0.215", features = ["derive"] } +serde_json = { version = "1.0.133" } +toml = { version = "0.8.19" } +anyhow = { version = "1.0.95" } +colored = { version = "3.0.0" } +regex = { version = "1.11.1" } +derive-new = { version = "0.7.0" } +derive_builder = { version = "0.20.2" } diff --git a/Dioxus.toml b/packages/frontend/Dioxus.toml similarity index 86% rename from Dioxus.toml rename to packages/frontend/Dioxus.toml index 2d509e5..2cf495e 100644 --- a/Dioxus.toml +++ b/packages/frontend/Dioxus.toml @@ -12,4 +12,4 @@ index_on_404 = true [bundle] publisher = "TheSimpleAIProject" -icon = [ "assets/icons/icon.png" ] \ No newline at end of file +icon = [ "assets/icons/icon.png" ] diff --git a/assets/icons/icon.png b/packages/frontend/assets/icons/icon.png similarity index 100% rename from assets/icons/icon.png rename to packages/frontend/assets/icons/icon.png diff --git a/assets/icons/icon.svg b/packages/frontend/assets/icons/icon.svg similarity index 100% rename from assets/icons/icon.svg rename to packages/frontend/assets/icons/icon.svg diff --git a/assets/themes/icons/con1/components/searchresult/sample.svg b/packages/frontend/assets/style/components/connection.css similarity index 100% rename from assets/themes/icons/con1/components/searchresult/sample.svg rename to packages/frontend/assets/style/components/connection.css diff --git a/assets/themes/icons/con1/index.theme b/packages/frontend/assets/style/components/divider.css similarity index 100% rename from assets/themes/icons/con1/index.theme rename to packages/frontend/assets/style/components/divider.css diff --git a/assets/themes/icons/con1/pages/editor/sample.svg b/packages/frontend/assets/style/components/drag_area.css similarity index 100% rename from assets/themes/icons/con1/pages/editor/sample.svg rename to packages/frontend/assets/style/components/drag_area.css diff --git a/assets/themes/icons/con1/pages/new/sample.svg b/packages/frontend/assets/style/components/draggable.css similarity index 100% rename from assets/themes/icons/con1/pages/new/sample.svg rename to packages/frontend/assets/style/components/draggable.css diff --git a/assets/themes/icons/con1/pages/search/sample.svg b/packages/frontend/assets/style/components/labeled_box.css similarity index 100% rename from assets/themes/icons/con1/pages/search/sample.svg rename to packages/frontend/assets/style/components/labeled_box.css diff --git a/assets/themes/styles/cybr1/components/divider/config.css b/packages/frontend/assets/style/components/nav_button.css similarity index 100% rename from assets/themes/styles/cybr1/components/divider/config.css rename to packages/frontend/assets/style/components/nav_button.css diff --git a/assets/themes/styles/cybr1/components/search/config.css b/packages/frontend/assets/style/components/node.css similarity index 100% rename from assets/themes/styles/cybr1/components/search/config.css rename to packages/frontend/assets/style/components/node.css diff --git a/assets/themes/styles/cybr1/components/searchresult/config.css b/packages/frontend/assets/style/components/runtime_param.css similarity index 100% rename from assets/themes/styles/cybr1/components/searchresult/config.css rename to packages/frontend/assets/style/components/runtime_param.css diff --git a/assets/themes/styles/cybr1/components/staticparam/config.css b/packages/frontend/assets/style/components/search.css similarity index 100% rename from assets/themes/styles/cybr1/components/staticparam/config.css rename to packages/frontend/assets/style/components/search.css diff --git a/assets/themes/styles/cybr1/components/staticparam/index.css b/packages/frontend/assets/style/components/search_result.css similarity index 100% rename from assets/themes/styles/cybr1/components/staticparam/index.css rename to packages/frontend/assets/style/components/search_result.css diff --git a/assets/themes/styles/cybr1/icon.ico b/packages/frontend/assets/style/components/static_param.css similarity index 100% rename from assets/themes/styles/cybr1/icon.ico rename to packages/frontend/assets/style/components/static_param.css diff --git a/assets/themes/styles/cybr1/index.theme b/packages/frontend/assets/style/components/viewport.css similarity index 100% rename from assets/themes/styles/cybr1/index.theme rename to packages/frontend/assets/style/components/viewport.css diff --git a/assets/themes/styles/cybr1/pages/editor/config.css b/packages/frontend/assets/style/pages/editor.css similarity index 100% rename from assets/themes/styles/cybr1/pages/editor/config.css rename to packages/frontend/assets/style/pages/editor.css diff --git a/assets/themes/styles/cybr1/pages/new/config.css b/packages/frontend/assets/style/pages/new.css similarity index 100% rename from assets/themes/styles/cybr1/pages/new/config.css rename to packages/frontend/assets/style/pages/new.css diff --git a/assets/themes/styles/cybr1/pages/search/config.css b/packages/frontend/assets/style/pages/search.css similarity index 100% rename from assets/themes/styles/cybr1/pages/search/config.css rename to packages/frontend/assets/style/pages/search.css diff --git a/assets/themes/styles/cybr1/pages/start/config.css b/packages/frontend/assets/style/pages/start.css similarity index 100% rename from assets/themes/styles/cybr1/pages/start/config.css rename to packages/frontend/assets/style/pages/start.css diff --git a/packages/frontend/assets/themes/icons/con1/components/search/sample.svg b/packages/frontend/assets/themes/icons/con1/components/search/sample.svg new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/assets/themes/icons/con1/components/searchresult/sample.svg b/packages/frontend/assets/themes/icons/con1/components/searchresult/sample.svg new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/assets/themes/icons/con1/index.theme b/packages/frontend/assets/themes/icons/con1/index.theme new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/assets/themes/icons/con1/pages/editor/sample.svg b/packages/frontend/assets/themes/icons/con1/pages/editor/sample.svg new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/assets/themes/icons/con1/pages/new/sample.svg b/packages/frontend/assets/themes/icons/con1/pages/new/sample.svg new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/assets/themes/icons/con1/pages/search/sample.svg b/packages/frontend/assets/themes/icons/con1/pages/search/sample.svg new file mode 100644 index 0000000..e69de29 diff --git a/assets/themes/icons/con1/pages/start/editor.svg b/packages/frontend/assets/themes/icons/con1/pages/start/editor.svg similarity index 100% rename from assets/themes/icons/con1/pages/start/editor.svg rename to packages/frontend/assets/themes/icons/con1/pages/start/editor.svg diff --git a/assets/themes/icons/con1/pages/start/new.svg b/packages/frontend/assets/themes/icons/con1/pages/start/new.svg similarity index 100% rename from assets/themes/icons/con1/pages/start/new.svg rename to packages/frontend/assets/themes/icons/con1/pages/start/new.svg diff --git a/assets/themes/icons/con1/pages/start/search.svg b/packages/frontend/assets/themes/icons/con1/pages/start/search.svg similarity index 100% rename from assets/themes/icons/con1/pages/start/search.svg rename to packages/frontend/assets/themes/icons/con1/pages/start/search.svg diff --git a/assets/themes/styles/cybr1/colors.css b/packages/frontend/assets/themes/styles/cybr1/colors.css similarity index 100% rename from assets/themes/styles/cybr1/colors.css rename to packages/frontend/assets/themes/styles/cybr1/colors.css diff --git a/assets/themes/styles/cybr1/components/connection/config.css b/packages/frontend/assets/themes/styles/cybr1/components/connection/config.css similarity index 100% rename from assets/themes/styles/cybr1/components/connection/config.css rename to packages/frontend/assets/themes/styles/cybr1/components/connection/config.css diff --git a/assets/themes/styles/cybr1/components/connection/index.css b/packages/frontend/assets/themes/styles/cybr1/components/connection/index.css similarity index 100% rename from assets/themes/styles/cybr1/components/connection/index.css rename to packages/frontend/assets/themes/styles/cybr1/components/connection/index.css diff --git a/packages/frontend/assets/themes/styles/cybr1/components/divider/config.css b/packages/frontend/assets/themes/styles/cybr1/components/divider/config.css new file mode 100644 index 0000000..e69de29 diff --git a/assets/themes/styles/cybr1/components/divider/index.css b/packages/frontend/assets/themes/styles/cybr1/components/divider/index.css similarity index 100% rename from assets/themes/styles/cybr1/components/divider/index.css rename to packages/frontend/assets/themes/styles/cybr1/components/divider/index.css diff --git a/assets/themes/styles/cybr1/components/node/config.css b/packages/frontend/assets/themes/styles/cybr1/components/node/config.css similarity index 100% rename from assets/themes/styles/cybr1/components/node/config.css rename to packages/frontend/assets/themes/styles/cybr1/components/node/config.css diff --git a/assets/themes/styles/cybr1/components/node/index.css b/packages/frontend/assets/themes/styles/cybr1/components/node/index.css similarity index 100% rename from assets/themes/styles/cybr1/components/node/index.css rename to packages/frontend/assets/themes/styles/cybr1/components/node/index.css diff --git a/assets/themes/styles/cybr1/components/runtimeparam/config.css b/packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/config.css similarity index 100% rename from assets/themes/styles/cybr1/components/runtimeparam/config.css rename to packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/config.css diff --git a/assets/themes/styles/cybr1/components/runtimeparam/index.css b/packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/index.css similarity index 100% rename from assets/themes/styles/cybr1/components/runtimeparam/index.css rename to packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/index.css diff --git a/packages/frontend/assets/themes/styles/cybr1/components/search/config.css b/packages/frontend/assets/themes/styles/cybr1/components/search/config.css new file mode 100644 index 0000000..e69de29 diff --git a/assets/themes/styles/cybr1/components/search/index.css b/packages/frontend/assets/themes/styles/cybr1/components/search/index.css similarity index 100% rename from assets/themes/styles/cybr1/components/search/index.css rename to packages/frontend/assets/themes/styles/cybr1/components/search/index.css diff --git a/packages/frontend/assets/themes/styles/cybr1/components/searchresult/config.css b/packages/frontend/assets/themes/styles/cybr1/components/searchresult/config.css new file mode 100644 index 0000000..e69de29 diff --git a/assets/themes/styles/cybr1/components/searchresult/index.css b/packages/frontend/assets/themes/styles/cybr1/components/searchresult/index.css similarity index 100% rename from assets/themes/styles/cybr1/components/searchresult/index.css rename to packages/frontend/assets/themes/styles/cybr1/components/searchresult/index.css diff --git a/packages/frontend/assets/themes/styles/cybr1/components/staticparam/config.css b/packages/frontend/assets/themes/styles/cybr1/components/staticparam/config.css new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/assets/themes/styles/cybr1/components/staticparam/index.css b/packages/frontend/assets/themes/styles/cybr1/components/staticparam/index.css new file mode 100644 index 0000000..e69de29 diff --git a/assets/themes/styles/cybr1/config.css b/packages/frontend/assets/themes/styles/cybr1/config.css similarity index 100% rename from assets/themes/styles/cybr1/config.css rename to packages/frontend/assets/themes/styles/cybr1/config.css diff --git a/packages/frontend/assets/themes/styles/cybr1/icon.ico b/packages/frontend/assets/themes/styles/cybr1/icon.ico new file mode 100644 index 0000000..e69de29 diff --git a/assets/themes/styles/cybr1/index.css b/packages/frontend/assets/themes/styles/cybr1/index.css similarity index 100% rename from assets/themes/styles/cybr1/index.css rename to packages/frontend/assets/themes/styles/cybr1/index.css diff --git a/packages/frontend/assets/themes/styles/cybr1/index.theme b/packages/frontend/assets/themes/styles/cybr1/index.theme new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/editor/config.css b/packages/frontend/assets/themes/styles/cybr1/pages/editor/config.css new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/editor/index.css b/packages/frontend/assets/themes/styles/cybr1/pages/editor/index.css new file mode 100644 index 0000000..1b757d8 --- /dev/null +++ b/packages/frontend/assets/themes/styles/cybr1/pages/editor/index.css @@ -0,0 +1,36 @@ +@import "../index.css"; + +main { + all: revert; + align-items: center; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + height: 100%; + overflow: clip; + width: 100%; +} + +aside { + height: 100%; + margin: 0; + padding: 0; + width: 30%; +} + +h1 { + height: fit-content; + margin: 0; + padding: 0; + text-align: center; + width: 70%; +} + +section { + height: 100%; +} + +.Search { + height: 100%; +} + diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/new/config.css b/packages/frontend/assets/themes/styles/cybr1/pages/new/config.css new file mode 100644 index 0000000..e69de29 diff --git a/assets/themes/styles/cybr1/pages/new/index.css b/packages/frontend/assets/themes/styles/cybr1/pages/new/index.css similarity index 100% rename from assets/themes/styles/cybr1/pages/new/index.css rename to packages/frontend/assets/themes/styles/cybr1/pages/new/index.css diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/search/config.css b/packages/frontend/assets/themes/styles/cybr1/pages/search/config.css new file mode 100644 index 0000000..e69de29 diff --git a/assets/themes/styles/cybr1/pages/search/index.css b/packages/frontend/assets/themes/styles/cybr1/pages/search/index.css similarity index 100% rename from assets/themes/styles/cybr1/pages/search/index.css rename to packages/frontend/assets/themes/styles/cybr1/pages/search/index.css diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/start/config.css b/packages/frontend/assets/themes/styles/cybr1/pages/start/config.css new file mode 100644 index 0000000..e69de29 diff --git a/assets/themes/styles/cybr1/pages/start/index.css b/packages/frontend/assets/themes/styles/cybr1/pages/start/index.css similarity index 100% rename from assets/themes/styles/cybr1/pages/start/index.css rename to packages/frontend/assets/themes/styles/cybr1/pages/start/index.css diff --git a/packages/frontend/src/lib.rs b/packages/frontend/src/lib.rs new file mode 100644 index 0000000..cff0af7 --- /dev/null +++ b/packages/frontend/src/lib.rs @@ -0,0 +1,11 @@ +pub mod modules; + +pub mod prelude { + pub use super::modules::*; + pub(crate) use dioxus::html::geometry::{euclid::Vector2D, *}; + pub(crate) use dioxus::prelude::*; + pub(crate) type PageVector = Vector2D; + pub(crate) use dioxus::logger::tracing::*; + pub(crate) use global::context::*; + pub(crate) use simple_ai_macros::{component, page}; +} diff --git a/src/modules.rs b/packages/frontend/src/modules.rs similarity index 66% rename from src/modules.rs rename to packages/frontend/src/modules.rs index 483e7fd..7ae84f4 100644 --- a/src/modules.rs +++ b/packages/frontend/src/modules.rs @@ -1,6 +1,4 @@ -pub mod assets; pub mod components; pub mod config; pub mod global; pub mod pages; -pub mod platform; diff --git a/packages/frontend/src/modules/components.rs b/packages/frontend/src/modules/components.rs new file mode 100644 index 0000000..a671c6d --- /dev/null +++ b/packages/frontend/src/modules/components.rs @@ -0,0 +1,29 @@ +pub mod connection; +pub mod divider; +pub mod drag_area; +pub mod draggable; +pub mod labeled_box; +pub mod nav_button; +pub mod node; +pub mod runtime_param; +pub mod search; +pub mod search_result; +pub mod static_param; +pub mod viewport; + +pub mod prelude { + pub use super::divider::*; + pub use super::drag_area::*; + pub use super::draggable::*; + pub use super::labeled_box::*; + pub use super::nav_button::*; + pub use super::node::*; + pub use super::search::*; + pub use super::search_result::*; + pub use super::viewport::*; + pub mod params { + pub use super::super::connection::*; + pub use super::super::runtime_param::*; + pub use super::super::static_param::*; + } +} diff --git a/src/modules/components/connection.rs b/packages/frontend/src/modules/components/connection.rs similarity index 89% rename from src/modules/components/connection.rs rename to packages/frontend/src/modules/components/connection.rs index 133725a..1aa1d71 100644 --- a/src/modules/components/connection.rs +++ b/packages/frontend/src/modules/components/connection.rs @@ -1,6 +1,5 @@ -use dioxus::html::geometry::{euclid::*, *}; -use dioxus::prelude::*; -use sai_backend::utils::prelude::*; +use crate::prelude::*; +use simple_ai_backend::utils::prelude::*; #[derive(PartialEq, Props, Clone)] pub struct InternConnection { @@ -18,9 +17,9 @@ impl From for InternConnection { } } -#[sai_macros::element("component")] -pub fn Connection(style: String, intern: InternConnection) -> Element { - let mut stroke_width = use_signal(|| 3); +#[component] +pub fn Connection(intern: InternConnection) -> Element { + let stroke_width = use_signal(|| 3); let mut left = use_signal(|| "unset"); let mut right = use_signal(|| "unset"); @@ -82,14 +81,18 @@ pub fn Connection(style: String, intern: InternConnection) -> Element { }; let div_mouseup = move |e: MouseEvent| { - *crate::global::context::CONNECTION.write() = Some(intern.clone()); + *CONNECTION.write() = Some(intern.clone()); }; let div_mounted = move |e: MountedEvent| async move { - div_rect.set(e.data().get_client_rect().await.unwrap_or(Rect::zero())); + div_rect.set( + e.data() + .get_client_rect() + .await + .unwrap_or(PixelsRect::zero()), + ); }; rsx! { - style { { style } } body { class: "Connection", display: "block", diff --git a/src/modules/components/divider.rs b/packages/frontend/src/modules/components/divider.rs similarity index 64% rename from src/modules/components/divider.rs rename to packages/frontend/src/modules/components/divider.rs index abd5e61..81a800c 100644 --- a/src/modules/components/divider.rs +++ b/packages/frontend/src/modules/components/divider.rs @@ -1,5 +1,7 @@ -#[sai_macros::element("component")] -pub fn Divider(style: String, children: Element) -> Element { +use crate::prelude::*; + +#[component] +pub fn Divider(children: Element) -> Element { let script = r#####" ((c) =>{ console.log("hello"); @@ -15,11 +17,10 @@ pub fn Divider(style: String, children: Element) -> Element { })(document.currentScript); "#####; rsx! { - style { { style } } - div { - class: "Divider", - script { { script } } - { children } - } + div { + class: "Divider", + script { { script } } + { children } + } } } diff --git a/packages/frontend/src/modules/components/drag_area.rs b/packages/frontend/src/modules/components/drag_area.rs new file mode 100644 index 0000000..cc72f42 --- /dev/null +++ b/packages/frontend/src/modules/components/drag_area.rs @@ -0,0 +1,54 @@ +use crate::prelude::*; + +#[derive(Default, Clone, Copy)] +pub struct DragContext { + pub cursor_start_position: Signal, + pub dragging: Signal, + pub distance: Signal, +} +impl DragContext { + pub fn new() -> Self { + Self::default() + } + pub fn init(&mut self, e: MouseEvent) { + self.dragging.set(true); + self.cursor_start_position + .set(e.page_coordinates().to_vector()); + } + pub fn moving(&mut self, e: MouseEvent) { + if self.dragging() { + self.distance + .set(e.page_coordinates().to_vector() - self.cursor_start_position.cloned()); + } + } + pub fn end(&mut self, e: MouseEvent) { + self.moving(e); + self.dragging.set(false); + self.distance.take(); + } + pub fn dragging(&self) -> bool { + self.dragging.cloned() + } + pub fn distance(&self) -> PageVector { + self.distance.cloned() + } +} + +#[component] +pub fn DragArea(children: Element) -> Element { + let mut context = use_context_provider(DragContext::new); + let mousedown = move |e: MouseEvent| context.init(e); + let mousemove = move |e: MouseEvent| context.moving(e); + let mouseup = move |e: MouseEvent| { + context.end(e); + }; + rsx! { + div { + class: "DragArea", + onmousedown: mousedown, + onmousemove: mousemove, + onmouseup: mouseup, + { children } + } + } +} diff --git a/packages/frontend/src/modules/components/draggable.rs b/packages/frontend/src/modules/components/draggable.rs new file mode 100644 index 0000000..995dd74 --- /dev/null +++ b/packages/frontend/src/modules/components/draggable.rs @@ -0,0 +1,88 @@ +use crate::prelude::{components::prelude::*, *}; + +#[component] +pub fn Draggable( + #[props(default)] ondraggingstart: Callback, + #[props(default)] ondragging: Callback, + #[props(default)] ondraggingend: Callback, + #[props(default)] position_save: Signal, + #[props(default)] position_handle: Signal, + #[props(default)] style_position_save: Signal<&'static str>, + #[props(default = Signal::new("relative"))] style_position_handle: Signal<&'static str>, + #[props(default)] z_index_handle: Signal, + #[props(default)] display_handle: Signal<&'static str>, + #[props(default)] user_select_handle: Signal<&'static str>, + #[props(default)] cursor_handle: Signal<&'static str>, + #[props(default)] pressed: Signal, + #[props(default)] mounted_data: Signal>>, + children: Element, +) -> Element { + let get_client_rect = move || async move { + if let Some(data) = mounted_data() { + if let Ok(rect) = data.get_client_rect().await { + return rect; + } + } + PixelsRect::default() + }; + + let context: DragContext = use_context(); + let mousedown = move |_| async move { + style_position_save.set(style_position_handle()); + position_handle.set(get_client_rect().await.origin.to_vector().cast_unit()); + style_position_handle.set("absolute"); + position_save.set(position_handle()); + user_select_handle.set("none"); + pressed.set(true); + ondraggingstart.call(position_save()); + cursor_handle.set("alias"); + }; + use_resource(move || async move { + if pressed() { + let position = position_save() + context.distance(); + if context.dragging() { + position_handle.set(position); + ondragging.call(position); + cursor_handle.set("alias"); + } else { + let rect = get_client_rect().await; + ondraggingend.call( + *position_handle.peek() + PageVector::new(rect.width(), rect.height()) / 2f64, + ); + position_handle.set(Vector2D::zero()); + style_position_handle.set(style_position_save()); + user_select_handle.set("unset"); + *pressed.write_silent() = false; + cursor_handle.set("unset"); + } + } + }); + + let mounted = move |e: MountedEvent| async move { + mounted_data.set(Some(e.data())); + }; + + let mouseover = move |_| { + cursor_handle.set("grab"); + }; + + rsx! { + div { + class: "Draggable", + width: "fit-content", + height: "fit-content", + top: 0, + left: 0, + z_index: z_index_handle, + display: "{display_handle}", + position: "{style_position_handle}", + user_select: "{user_select_handle}", + cursor: "{cursor_handle}", + transform: "translate({position_handle.read().x}px, {position_handle.read().y}px)", + onmousedown: mousedown, + onmounted: mounted, + onmouseover: mouseover, + { children } + } + } +} diff --git a/src/modules/components/labeled_box.rs b/packages/frontend/src/modules/components/labeled_box.rs similarity index 93% rename from src/modules/components/labeled_box.rs rename to packages/frontend/src/modules/components/labeled_box.rs index 327300c..4c9770f 100644 --- a/src/modules/components/labeled_box.rs +++ b/packages/frontend/src/modules/components/labeled_box.rs @@ -1,4 +1,6 @@ -#[sai_macros::element("component")] +use crate::prelude::*; + +#[component] pub fn LabeledBox(children: Element) -> Element { let script = r#####" ((c) =>{ diff --git a/packages/frontend/src/modules/components/nav_button.rs b/packages/frontend/src/modules/components/nav_button.rs new file mode 100644 index 0000000..9a714a6 --- /dev/null +++ b/packages/frontend/src/modules/components/nav_button.rs @@ -0,0 +1,20 @@ +use crate::prelude::*; +use dioxus::router::prelude::*; + +#[component] +pub fn NavButton( + children: Element, + class: Option, + #[props(into)] to: NavigationTarget, +) -> Element { + let class_unw = class.unwrap_or_default(); + rsx! { + Link { + class: "NavbarButton {class_unw}", + to: to, + div { + {children} + } + } + } +} diff --git a/src/modules/components/node.rs b/packages/frontend/src/modules/components/node.rs similarity index 56% rename from src/modules/components/node.rs rename to packages/frontend/src/modules/components/node.rs index 9379bd5..f38c470 100644 --- a/src/modules/components/node.rs +++ b/packages/frontend/src/modules/components/node.rs @@ -1,7 +1,5 @@ -use super::*; -use dioxus::html::geometry::{euclid::Vector2D, *}; -use dioxus::prelude::*; -use sai_backend::utils::prelude::*; +use crate::prelude::{components::prelude::params::*, *}; +use simple_ai_backend::utils::prelude::*; #[derive(PartialEq, Props, Clone)] pub struct InternNode { @@ -19,32 +17,46 @@ pub struct InternNode { } impl From for InternNode { - fn from(node: StrongNode) -> Self { - let b = Self::builder().node(node.clone()); - // if let Ok(data) = node.context.lock() { - // return b - // .params(Signal::new( - // data.params - // .iter() - // .map(|param| InternParam::from(param.clone())) - // .collect(), - // )) - // .build(); - // } + fn from(node_ctx: StrongNode) -> Self { + let b = Self::builder() + .node(node_ctx.clone()) + .position(Signal::new(PageVector::from( + node_ctx + .context + .try_lock() + .unwrap() + .position + .unwrap_or_default(), + ))); + let node = node_ctx.context.try_lock().unwrap(); + let mut runtime_params = Vec::::new(); + let mut static_params = Vec::::new(); + node.params.iter().for_each(move |param_ctx| { + let param = param_ctx.context.try_lock().unwrap(); + match param.kind { + ParamKind::Runtime { .. } => { + runtime_params.push(InternRuntimeParam::from(param_ctx.clone())); + } + ParamKind::Static { .. } => { + static_params.push(InternStaticParam::from(param_ctx.clone())); + } + } + }); + dioxus::logger::tracing::debug!("creating"); b.build() } } -#[sai_macros::element("component")] -pub fn Node(style: String, intern: InternNode) -> Element { +#[component] +pub fn Node(intern: InternNode) -> Element { let mousedown = move |_| { intern.pressed.set(true); }; - let mounted = move |e: MountedEvent| { - // intern.node.params.iter().for_each(|param| { - // intern.params.push(InternParam::from(param.clone())); - // }); + let mounted = move |e: MountedEvent| async move { + if let Ok(client_rect) = e.data().get_client_rect().await { + intern.position -= PageVector::new(client_rect.width(), client_rect.width()) / 2f64; + } }; let mut node_name = use_signal(String::new); @@ -63,7 +75,6 @@ pub fn Node(style: String, intern: InternNode) -> Element { .map(|intern| rsx! { RuntimeParam { intern: intern.clone() } }); rsx! { - style { { style } } body { class: "Node", position: "absolute", diff --git a/packages/frontend/src/modules/components/runtime_param.rs b/packages/frontend/src/modules/components/runtime_param.rs new file mode 100644 index 0000000..2ac7a33 --- /dev/null +++ b/packages/frontend/src/modules/components/runtime_param.rs @@ -0,0 +1,34 @@ +use crate::prelude::{components::prelude::params::*, *}; +use simple_ai_backend::utils::prelude::*; + +#[derive(PartialEq, Props, Clone)] +pub struct InternRuntimeParam { + pub param: StrongParam, + pub connection: Signal, +} +impl From for InternRuntimeParam { + fn from(param_ctx: StrongParam) -> Self { + let param = param_ctx.context.blocking_lock(); + let mut runtime_param_kind = RuntimeParamKind::Input; // This will not happen just so that no error + // is thrown + if let ParamKind::Runtime { kind, .. } = ¶m.kind { + runtime_param_kind = kind.clone() + } + Self::builder() + .param(param_ctx.clone()) + .connection(Signal::new(InternConnection::from( + runtime_param_kind.clone(), + ))) + .build() + } +} + +#[component] +pub fn RuntimeParam(intern: InternRuntimeParam) -> Element { + rsx! { + body { + class: "Param", + Connection { intern: (intern.connection)() } + } + } +} diff --git a/packages/frontend/src/modules/components/search.rs b/packages/frontend/src/modules/components/search.rs new file mode 100644 index 0000000..4831026 --- /dev/null +++ b/packages/frontend/src/modules/components/search.rs @@ -0,0 +1,55 @@ +use crate::prelude::{ + components::prelude::{InternSearchResult, SearchResult}, + *, +}; +use simple_ai_backend::utils::prelude::{search::*, *}; + +#[component] +pub fn Search() -> Element { + let mut intern_search_results = use_signal(Vec::::new); + let mut search_results = use_signal(Vec::::new); + + let input = move |e: FormEvent| { + search_results.set(search(e.value())); + intern_search_results.clear(); + }; + + use_effect(move || { + intern_search_results.set( + search_results() + .iter_mut() + .map(|result| InternSearchResult::from(result.clone())) + .collect::>(), + ); + }); + + rsx! { + div { + overflow: "visible", + class: "Search", + header { + input { + oninput: input, + type: "search", + placeholder: "search" + } + // nav { + // button { "your nodes" } + // button { "installed nodes" } + // button { "profiles" } + // } + } + main { + overflow: "visible", + div { class: "spacer" } + section { + class: "results", + height: "100%", + for intern in intern_search_results() { + SearchResult { intern } + } + } + } + } + } +} diff --git a/packages/frontend/src/modules/components/search_result.rs b/packages/frontend/src/modules/components/search_result.rs new file mode 100644 index 0000000..8269ab0 --- /dev/null +++ b/packages/frontend/src/modules/components/search_result.rs @@ -0,0 +1,53 @@ +use crate::prelude::{components::prelude::Draggable, *}; +use simple_ai_backend::utils::prelude::*; + +#[derive(PartialEq, Props, Clone, Copy)] +pub struct InternSearchResult { + pub node: Signal, +} + +impl From for InternSearchResult { + fn from(node_context: Node) -> Self { + Self::builder().node(Signal::new(node_context)).build() + } +} + +#[component] +pub fn SearchResult(intern: InternSearchResult) -> Element { + let draggingend = move |v: PageVector| { + let mut node = intern.node.cloned(); + node.position = Some((v.x, v.y)); + *DRAG_NODE.write() = Some(node); + }; + + rsx! { + Draggable { + ondraggingend: draggingend, + div { + class: "SearchResult", + div { + class: "wrapper items", + h3 { span { id: "name", "{intern.node.cloned().name}" } } + + div { + class: "wrapper", + div { + class: "wrapper i", + + div { id: "open", class: "icon" } + } + } + } + h5 { id: "version", r#"{intern.node.cloned().date.format("%d/%m/%Y")}"# } + p { + id: "description", + "{intern.node.cloned().description}" + } + address { + id: "author", + "{intern.node.cloned().author}" + } + } + } + } +} diff --git a/packages/frontend/src/modules/components/static_param.rs b/packages/frontend/src/modules/components/static_param.rs new file mode 100644 index 0000000..3f375d2 --- /dev/null +++ b/packages/frontend/src/modules/components/static_param.rs @@ -0,0 +1,23 @@ +use crate::prelude::*; +use simple_ai_backend::utils::prelude::*; + +#[derive(PartialEq, Props, Clone)] +pub struct InternStaticParam { + pub param: StrongParam, +} +impl From for InternStaticParam { + fn from(param: StrongParam) -> Self { + let b = Self::builder(); + b.param(param).build() + } +} + +#[component] +pub fn StaticParam(style: String, intern: InternStaticParam) -> Element { + rsx! { + style { { style } } + body { + class: "Param", + } + } +} diff --git a/src/modules/components/viewport.rs b/packages/frontend/src/modules/components/viewport.rs similarity index 56% rename from src/modules/components/viewport.rs rename to packages/frontend/src/modules/components/viewport.rs index f987424..3106f46 100644 --- a/src/modules/components/viewport.rs +++ b/packages/frontend/src/modules/components/viewport.rs @@ -1,41 +1,49 @@ -use std::ops::DerefMut; - -use dioxus::html::geometry::{euclid::*, *}; -use sai_backend::utils::prelude::*; +use crate::prelude::*; +use simple_ai_backend::utils::prelude::*; #[derive(Clone)] pub struct ViewportNodeContainer { pub backend_node_container: NodeContainer, - pub frontend_node_container: Signal>, + pub frontend_node_container: Signal>, } impl ViewportNodeContainer { pub fn new() -> Self { - Self { - backend_node_container: NodeContainer::new(), - frontend_node_container: use_signal(Vec::::new), - } + Self::default() } pub fn push_context(&mut self, node_ctx: StrongNode) { self.backend_node_container.push_context(node_ctx.clone()); self.frontend_node_container - .push(super::InternNode::from(node_ctx)); + .push(super::node::InternNode::from(node_ctx)); + } +} +impl Default for ViewportNodeContainer { + fn default() -> Self { + Self { + backend_node_container: NodeContainer::new(), + frontend_node_container: use_signal(Vec::::new), + } } } -#[sai_macros::element("component")] -pub fn Viewport() -> Element { +#[component] +pub fn Viewport( + #[props(default)] node_container: Signal, + #[props(default)] mounted_data: Signal>>, +) -> Element { // ------------------------------ VARIABLES ------------------------------ // - let node_context = StrongContext::from(ViewportNodeContainer::new()); - - let nc_rendered_nodes = node_context.clone(); - let rendered_nodes = { - spawn(async move { - let nodes = nc_rendered_nodes.context.lock().await; - nodes - .frontend_node_container - .iter() - .map(|intern| rsx! { super::Node { intern: intern.clone() } }); - }); + let fnc = node_container().frontend_node_container.cloned(); + let rendered_nodes = fnc + .iter() + .map(|intern| rsx! { super::node::Node { intern: intern.clone() } }); + + let get_client_rect = move || async move { + if let Some(data) = mounted_data() { + if let Ok(rect) = data.get_client_rect().await { + dioxus::logger::tracing::debug!("RECT"); + return rect; + } + } + PixelsRect::default() }; let mut cursor_start_coords = use_signal(Vector2D::::zero); @@ -45,8 +53,8 @@ pub fn Viewport() -> Element { let mut scale = use_signal(|| 1f64); let mut pressed = use_signal(|| false); - let mut pressed_node = use_signal(|| Option::::None); - let mut pressed_connection = use_signal(|| Option::::None); + let mut pressed_node = use_signal(|| Option::::None); + let mut pressed_connection = use_signal(|| Option::::None); let mut cursor = use_signal(String::new); @@ -63,55 +71,47 @@ pub fn Viewport() -> Element { } }; - // ------------------------------ EVENTS ------------------------------ // - let nc_drop = node_context.clone(); - let drop = move |e: DragEvent| { - e.prevent_default(); - - let node = crate::global::context::DRAG_NODE.unwrap(); - // node.position.set(e.page_coordinates().to_vector()); - let nci = nc_drop.clone(); - spawn(async move { - let ncii = nci.clone(); - let mut nodes = ncii.context.lock().await; - nodes.deref_mut().push_context(node.clone()); - }); - }; + use_resource(move || async move { + let mut ctx = DRAG_NODE(); + if let Some(mut node) = ctx.take() { + dioxus::logger::tracing::debug!("NODE: {:?}", node.name); + let position = (PageVector::from(node.position.unwrap_or_default()) + - get_client_rect().await.origin.to_vector().cast_unit()) + / *scale.peek(); + node.position = Some((position.x, position.y)); + node_container().push_context(StrongNode::from(node)); + } + }); + // ------------------------------ EVENTS ------------------------------ // let dragover = move |e: DragEvent| { e.prevent_default(); }; - let nc_mousedown = node_context.clone(); let mousedown = move |e: MouseEvent| { - let nci = nc_mousedown.clone(); - spawn(async move { - let mut nodes = nci.context.lock().await; - for mut node in nodes.frontend_node_container.iter_mut() { - for param in node.runtime_params.iter_mut() { - if ((param.connection)().pressed)() { - // let mut param_context = param.param.context.lock().await; - cursor_start_coords.set(e.page_coordinates().to_vector()); - pressed_connection.set(Some((param.connection)().clone())); - return; - } - } - if (node.pressed)() { - node.cursor.set("grabbing".into()); + for node in node_container().frontend_node_container.cloned().iter_mut() { + for param in node.runtime_params.iter_mut() { + if ((param.connection)().pressed)() { cursor_start_coords.set(e.page_coordinates().to_vector()); - element_start_coords.set((node.position)() * scale()); - pressed_node.set(Some(node.clone())); + pressed_connection.set(Some((param.connection)().clone())); return; } } - if let Some(button) = e.trigger_button() { - if button.into_web_code() == 1 { - cursor_start_coords.set(e.page_coordinates().to_vector()); - element_start_coords.set(position()); - pressed.set(true); - } + if (node.pressed)() { + node.cursor.set("grabbing".into()); + cursor_start_coords.set(e.page_coordinates().to_vector()); + element_start_coords.set((node.position)() * scale()); + pressed_node.set(Some(node.clone())); + return; + } + } + if let Some(button) = e.trigger_button() { + if button.into_web_code() == 1 { + cursor_start_coords.set(e.page_coordinates().to_vector()); + element_start_coords.set(position()); + pressed.set(true); } - }); + } }; let mousemove = move |e: MouseEvent| { @@ -120,9 +120,6 @@ pub fn Viewport() -> Element { } else if let Some(mut node) = pressed_node() { node.cursor.set("grabbing".into()); node.position.set(get_node_coords(&e)); - // for mut param in node.runtime_params.iter_mut() { - // if let Some(connection) = (param.connection)() {} - // } } else if pressed() { cursor.set("move".into()); position.set(get_coords(&e)); @@ -133,7 +130,7 @@ pub fn Viewport() -> Element { if let Some(mut connection) = pressed_connection() { connection.dimensions.set(get_diff(&e)); connection.pressed.set(false); - if let Some(mut c) = crate::global::context::CONNECTION() { + if let Some(mut c) = CONNECTION() { c.foreign_dimensions.set((connection.dimensions)()); } pressed_connection.set(None); @@ -147,6 +144,7 @@ pub fn Viewport() -> Element { position.set(get_coords(&e)); pressed.set(false); } + dioxus::logger::tracing::debug!("Mouseup"); }; let wheel = move |e: WheelEvent| { @@ -163,10 +161,15 @@ pub fn Viewport() -> Element { dioxus::logger::tracing::debug!("sub: {sub}, scale: {scale}"); }; + let mounted = move |e: MountedEvent| async move { + mounted_data.set(Some(e.data())); + }; + rsx! { body { class: "Viewport", cursor: "{cursor}", + overflow: "hidden", ondrop: drop, ondragover: dragover, onmousedown: mousedown, @@ -180,6 +183,7 @@ pub fn Viewport() -> Element { left: 0, transform: "translate({position().x}px, {position().y}px) scale({scale()})", user_select: "none", + onmounted: mounted, { rendered_nodes } } } diff --git a/src/modules/config.rs b/packages/frontend/src/modules/config.rs similarity index 100% rename from src/modules/config.rs rename to packages/frontend/src/modules/config.rs diff --git a/packages/frontend/src/modules/global.rs b/packages/frontend/src/modules/global.rs new file mode 100644 index 0000000..5a2c63e --- /dev/null +++ b/packages/frontend/src/modules/global.rs @@ -0,0 +1,5 @@ +pub mod context; + +pub mod prelude { + pub use super::context::*; +} diff --git a/packages/frontend/src/modules/global/context.rs b/packages/frontend/src/modules/global/context.rs new file mode 100644 index 0000000..d5a6628 --- /dev/null +++ b/packages/frontend/src/modules/global/context.rs @@ -0,0 +1,5 @@ +use crate::prelude::{components::prelude::params::*, *}; +use simple_ai_backend::utils::prelude::*; + +pub(crate) static DRAG_NODE: GlobalSignal> = Signal::global(|| None); +pub(crate) static CONNECTION: GlobalSignal> = Signal::global(|| None); diff --git a/packages/frontend/src/modules/pages.rs b/packages/frontend/src/modules/pages.rs new file mode 100644 index 0000000..36808da --- /dev/null +++ b/packages/frontend/src/modules/pages.rs @@ -0,0 +1,11 @@ +pub mod editor; +pub mod new; +pub mod search; +pub mod start; + +pub mod prelude { + pub use super::editor::Editor; + pub use super::new::New; + pub use super::search::Search; + pub use super::start::Start; +} diff --git a/packages/frontend/src/modules/pages/editor.rs b/packages/frontend/src/modules/pages/editor.rs new file mode 100644 index 0000000..3583d5a --- /dev/null +++ b/packages/frontend/src/modules/pages/editor.rs @@ -0,0 +1,25 @@ +use crate::prelude::{components::prelude::*, *}; + +#[page] +pub fn Editor() -> Element { + rsx! { + style { "html {{overflow: hidden;}} * {{ user_select: none }}" } + main { + DragArea { + Divider + { + section { + Viewport {} + } + aside { + z_index: 2, + nav {} + section { + Search {} + } + } + } + } + } + } +} diff --git a/src/modules/pages/new.rs b/packages/frontend/src/modules/pages/new.rs similarity index 65% rename from src/modules/pages/new.rs rename to packages/frontend/src/modules/pages/new.rs index afff03e..cc72d57 100644 --- a/src/modules/pages/new.rs +++ b/packages/frontend/src/modules/pages/new.rs @@ -1,9 +1,8 @@ -#[sai_macros::element("page")] -pub fn New(style: String, icons: Icons) -> Element { - use crate::components::*; +use crate::prelude::{components::prelude::*, *}; +#[page] +pub fn New() -> Element { rsx! { - style { { style } } main { form { LabeledBox { @@ -40,24 +39,3 @@ pub fn New(style: String, icons: Icons) -> Element { } } } - -use crate::platform::Window; -pub fn NewWindow() -> Window { - #[cfg(feature = "desktop")] - { - Window::new(super::New, || { - use dioxus::desktop::{Config, LogicalSize, WindowBuilder}; - Config::default().with_menu(None).with_window( - WindowBuilder::new() - .with_inner_size(LogicalSize::new(680, 1000)) - .with_resizable(false) - .with_maximizable(false) - .with_transparent(true), - ) - }) - } - #[cfg(feature = "web")] - { - Window::new(NAME) - } -} diff --git a/packages/frontend/src/modules/pages/search.rs b/packages/frontend/src/modules/pages/search.rs new file mode 100644 index 0000000..e34bfc0 --- /dev/null +++ b/packages/frontend/src/modules/pages/search.rs @@ -0,0 +1,10 @@ +use crate::prelude::{components::prelude::*, *}; + +#[page] +pub fn Search() -> Element { + rsx! { + main { + Search {} + } + } +} diff --git a/packages/frontend/src/modules/pages/start.rs b/packages/frontend/src/modules/pages/start.rs new file mode 100644 index 0000000..b16190e --- /dev/null +++ b/packages/frontend/src/modules/pages/start.rs @@ -0,0 +1,45 @@ +pub use crate::prelude::{components::prelude::*, *}; +use dioxus::router::prelude::*; + +#[page] +pub fn Start( + #[props(into)] search_route: NavigationTarget, + #[props(into)] new_route: NavigationTarget, + #[props(into)] editor_route: NavigationTarget, +) -> Element { + rsx! { + main { + article { + section { + NavButton { class: "search", to: search_route, div { class: "icon search", + svg { + "viewBox": "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + fill: "currentColor", + path { d: "M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z" } + } + } } + NavButton { class: "new", to: new_route, div { class: "icon new", } + svg { + xmlns: "http://www.w3.org/2000/svg", + "viewBox": "0 0 24 24", + fill: "currentColor", + path { d: "M11 11V5H13V11H19V13H13V19H11V13H5V11H11Z" } + } + + } + NavButton { class: "editor", to: editor_route, div { class: "icon", id: "editor", + + svg { + "viewBox": "0 0 24 24", + fill: "currentColor", + xmlns: "http://www.w3.org/2000/svg", + path { d: "M21 6.75736L19 8.75736V4H10V9H5V20H19V17.2426L21 15.2426V21.0082C21 21.556 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5501 3 20.9932V8L9.00319 2H19.9978C20.5513 2 21 2.45531 21 2.9918V6.75736ZM21.7782 8.80761L23.1924 10.2218L15.4142 18L13.9979 17.9979L14 16.5858L21.7782 8.80761Z" } + } + + } } + } + } + } + } +} diff --git a/packages/macros/.gitignore b/packages/macros/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/packages/macros/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml new file mode 100644 index 0000000..39bc7e7 --- /dev/null +++ b/packages/macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "simple-ai-macros" +version = { workspace = true } +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { version = "1" } +syn = { version = "2.0", features = ["full", "printing", "parsing"] } +quote = { version = "1.0" } +change-case = { version = "0.2.0" } diff --git a/packages/macros/src/component.rs b/packages/macros/src/component.rs new file mode 100644 index 0000000..206b2f5 --- /dev/null +++ b/packages/macros/src/component.rs @@ -0,0 +1,6 @@ +use crate::element::element; +use proc_macro2::TokenStream; + +pub fn macro_impl(_attr: TokenStream, item: TokenStream) -> TokenStream { + element(item, "components") +} diff --git a/packages/macros/src/element.rs b/packages/macros/src/element.rs new file mode 100644 index 0000000..e669d6f --- /dev/null +++ b/packages/macros/src/element.rs @@ -0,0 +1,52 @@ +use change_case::*; +use proc_macro2::{Span, TokenStream}; +use quote::*; +use syn::{parse2, parse_quote, Block, Ident, ItemFn, LitStr, Stmt}; + +pub fn element(item: TokenStream, str_type_name: &str) -> TokenStream { + let mut out_items: ItemFn = parse2(item).unwrap(); + + let fn_name = &out_items.sig.ident; + + let str_emt_name = constant_case(&fn_name.to_string()); + let str_lowercase_emt_name = snake_case(&str_emt_name); + let str_css_emt_name = str_emt_name + "_CSS"; + let css_emt_name = Ident::new(&str_css_emt_name, Span::call_site()); + + let str_asset_path = std::path::PathBuf::from("/assets") + .join("style") + .join(str_type_name) + .join(str_lowercase_emt_name + ".css"); + + let asset_path = LitStr::new( + str_asset_path.to_str().unwrap_or_default(), + Span::call_site(), + ); + + for stmt in &mut out_items.block.stmts { + if let Stmt::Expr(syn::Expr::Macro(macro_expr), _) = stmt { + if macro_expr.mac.path.is_ident("rsx") { + let rsx_tokens = macro_expr.mac.tokens.clone(); + + macro_expr.mac.tokens = { + let mut rsx_block: Block = + parse2(rsx_tokens).expect("Failed to parse rsx! block"); + + let new_link: Stmt = parse_quote! { + document::Link { rel: "stylesheet", href: NAVBAR_CSS }, + }; + + rsx_block.stmts.insert(0, new_link); + + rsx_block.to_token_stream() + }; + } + } + } + + quote! { + const #css_emt_name: Asset = asset!(#asset_path); + #[dioxus::core_macro::component] + #out_items + } +} diff --git a/extern/macros/src/lib.rs b/packages/macros/src/lib.rs similarity index 84% rename from extern/macros/src/lib.rs rename to packages/macros/src/lib.rs index 4dc2310..239a657 100644 --- a/extern/macros/src/lib.rs +++ b/packages/macros/src/lib.rs @@ -1,3 +1,5 @@ +mod element; + macro_rules! create_attr_macro { ($name: ident) => { mod $name; @@ -11,4 +13,5 @@ macro_rules! create_attr_macro { }; } -create_attr_macro!(element); +create_attr_macro!(component); +create_attr_macro!(page); diff --git a/packages/macros/src/page.rs b/packages/macros/src/page.rs new file mode 100644 index 0000000..7c4aaa9 --- /dev/null +++ b/packages/macros/src/page.rs @@ -0,0 +1,6 @@ +use crate::element::element; +use proc_macro2::TokenStream; + +pub fn macro_impl(_attr: TokenStream, item: TokenStream) -> TokenStream { + element(item, "pages") +} diff --git a/packages/platforms/desktop/.gitignore b/packages/platforms/desktop/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/packages/platforms/desktop/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/packages/platforms/desktop/Cargo.toml b/packages/platforms/desktop/Cargo.toml new file mode 100644 index 0000000..12e8ff2 --- /dev/null +++ b/packages/platforms/desktop/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "simple-ai-desktop" +version = { workspace = true } +edition = "2021" + +[dependencies] +dioxus = { workspace = true, features = ["desktop"]} +simple-ai-frontend = { workspace = true } diff --git a/packages/platforms/desktop/src/main.rs b/packages/platforms/desktop/src/main.rs new file mode 100644 index 0000000..4e7a855 --- /dev/null +++ b/packages/platforms/desktop/src/main.rs @@ -0,0 +1,18 @@ +mod router; + +pub mod prelude { + pub(crate) use dioxus::prelude::*; + pub(crate) use simple_ai_frontend::prelude::*; +} + +use prelude::*; +use router::Route; + +fn main() { + dioxus::logger::init(dioxus::logger::tracing::Level::DEBUG).expect("failed to init logger"); + dioxus::launch(App); +} + +fn App() -> Element { + rsx! { Router:: {} } +} diff --git a/packages/platforms/desktop/src/router.rs b/packages/platforms/desktop/src/router.rs new file mode 100644 index 0000000..b12d7f4 --- /dev/null +++ b/packages/platforms/desktop/src/router.rs @@ -0,0 +1,37 @@ +use crate::prelude::{pages::prelude::*, *}; + +fn StartPage() -> Element { + rsx! { + Start { + search_route: Route::SearchPage {}, + new_route: Route::NewPage {}, + editor_route: Route::EditorPage {}, + } + } +} + +fn SearchPage() -> Element { + rsx! { Search {} } +} + +fn NewPage() -> Element { + rsx! { New {} } +} + +fn EditorPage() -> Element { + rsx! { Editor {} } +} + +#[derive(Debug, Clone, Routable, PartialEq)] +#[rustfmt::skip] +pub enum Route { + #[redirect("/", || Route::StartPage {}) ] + #[route("/start")] + StartPage {}, + #[route("/search")] + SearchPage {}, + #[route("/new")] + NewPage {}, + #[route("/editor")] + EditorPage {}, +} diff --git a/packages/platforms/web/.gitignore b/packages/platforms/web/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/packages/platforms/web/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/packages/platforms/web/Cargo.toml b/packages/platforms/web/Cargo.toml new file mode 100644 index 0000000..f88635e --- /dev/null +++ b/packages/platforms/web/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "simple-ai-web" +version = { workspace = true } +edition = "2021" + +[dependencies] +dioxus = { workspace = true, features = ["web", "router"]} +simple-ai-frontend = { workspace = true } diff --git a/packages/platforms/web/src/main.rs b/packages/platforms/web/src/main.rs new file mode 100644 index 0000000..8576ed6 --- /dev/null +++ b/packages/platforms/web/src/main.rs @@ -0,0 +1,18 @@ +mod router; + +pub mod prelude { + pub(crate) use dioxus::prelude::*; + pub(crate) use simple_ai_frontend::prelude::*; +} + +use prelude::*; +use router::Route; + +fn main() { + dioxus::logger::init(dioxus::logger::tracing::Level::DEBUG).expect("failed to init logger"); + platform::launch(App); +} + +fn App() -> Element { + rsx! { Router:: {} } +} diff --git a/packages/platforms/web/src/router.rs b/packages/platforms/web/src/router.rs new file mode 100644 index 0000000..b12d7f4 --- /dev/null +++ b/packages/platforms/web/src/router.rs @@ -0,0 +1,37 @@ +use crate::prelude::{pages::prelude::*, *}; + +fn StartPage() -> Element { + rsx! { + Start { + search_route: Route::SearchPage {}, + new_route: Route::NewPage {}, + editor_route: Route::EditorPage {}, + } + } +} + +fn SearchPage() -> Element { + rsx! { Search {} } +} + +fn NewPage() -> Element { + rsx! { New {} } +} + +fn EditorPage() -> Element { + rsx! { Editor {} } +} + +#[derive(Debug, Clone, Routable, PartialEq)] +#[rustfmt::skip] +pub enum Route { + #[redirect("/", || Route::StartPage {}) ] + #[route("/start")] + StartPage {}, + #[route("/search")] + SearchPage {}, + #[route("/new")] + NewPage {}, + #[route("/editor")] + EditorPage {}, +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 8f0560f..0000000 --- a/src/main.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod modules; -pub use modules::*; - -fn main() { - dioxus::logger::init(dioxus::logger::tracing::Level::DEBUG).expect("failed to init logger"); - - #[cfg(any(feature = "web", feature = "desktop"))] - platform::launch(); -} diff --git a/src/modules/assets.rs b/src/modules/assets.rs deleted file mode 100644 index 5f42e26..0000000 --- a/src/modules/assets.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod dirs; -pub mod files; -pub mod include; diff --git a/src/modules/assets/dirs.rs b/src/modules/assets/dirs.rs deleted file mode 100644 index aabdf13..0000000 --- a/src/modules/assets/dirs.rs +++ /dev/null @@ -1,37 +0,0 @@ -pub use std::path::PathBuf; - -pub type IncludedDir = &'static include_dir::Dir<'static>; - -pub mod paths { - use super::*; - - pub fn assets() -> PathBuf { - PathBuf::new() - } - - pub fn themes() -> PathBuf { - assets().join("themes") - } - - pub fn icons() -> PathBuf { - themes().join("icons") - } - - pub fn styles() -> PathBuf { - themes().join("styles") - } - - pub fn current_icons() -> PathBuf { - icons().join(crate::config::DEFAULT_ICONS) - } - - pub fn current_style() -> PathBuf { - styles().join(crate::config::DEFAULT_STYLES) - } -} - -pub fn get_dir(path: PathBuf) -> IncludedDir { - super::include::ASSETS - .get_dir(&path) - .expect(format!("Couldn't get dir: {}", path.display()).as_str()) -} diff --git a/src/modules/assets/files.rs b/src/modules/assets/files.rs deleted file mode 100644 index a83e99e..0000000 --- a/src/modules/assets/files.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub use std::path::PathBuf; - -pub type IncludedFile = &'static include_dir::File<'static>; - -pub fn get_file(path: PathBuf) -> IncludedFile { - super::include::ASSETS - .get_file(&path) - .expect(format!("Couldn't get file: {}", path.display()).as_str()) -} diff --git a/src/modules/assets/include.rs b/src/modules/assets/include.rs deleted file mode 100644 index 5d28446..0000000 --- a/src/modules/assets/include.rs +++ /dev/null @@ -1,3 +0,0 @@ -use include_dir::{include_dir, Dir}; - -pub static ASSETS: Dir = include_dir!("$CARGO_MANIFEST_DIR/assets"); diff --git a/src/modules/components.rs b/src/modules/components.rs deleted file mode 100644 index d59d4c6..0000000 --- a/src/modules/components.rs +++ /dev/null @@ -1,17 +0,0 @@ -mod connection; -mod divider; -mod labeled_box; -mod node; -mod params; -mod search; -mod search_result; -mod viewport; - -pub use connection::*; -pub use divider::*; -pub use labeled_box::*; -pub use node::*; -pub use params::*; -pub use search::*; -pub use search_result::*; -pub use viewport::*; diff --git a/src/modules/components/params.rs b/src/modules/components/params.rs deleted file mode 100644 index 512d942..0000000 --- a/src/modules/components/params.rs +++ /dev/null @@ -1,52 +0,0 @@ -use sai_backend::utils::prelude::*; - -#[derive(PartialEq, Props, Clone)] -pub struct InternRuntimeParam { - pub param: StrongParam, - pub connection: Signal, -} -impl From for InternRuntimeParam { - fn from(param: StrongParam) -> Self { - let b = Self::builder().param(param.clone()); - if let Ok(data) = param.context.try_lock() { - if let ParamKind::Runtime { kind, .. } = &data.kind { - return b - .connection(Signal::new(super::InternConnection::from(kind.clone()))) - .build(); - } - } - panic!("Couldn't create Param"); - } -} - -#[sai_macros::element("component")] -pub fn RuntimeParam(style: String, intern: InternRuntimeParam) -> Element { - rsx! { - style { { style } } - body { - class: "Param", - super::Connection { intern: (intern.connection)() } - } - } -} - -#[derive(PartialEq, Props, Clone)] -pub struct InternStaticParam { - pub param: StrongParam, -} -impl From for InternStaticParam { - fn from(param: StrongParam) -> Self { - let b = Self::builder(); - b.param(param).build() - } -} - -#[sai_macros::element("component")] -pub fn StaticParam(style: String, intern: InternStaticParam) -> Element { - rsx! { - style { { style } } - body { - class: "Param", - } - } -} diff --git a/src/modules/components/search_result.rs b/src/modules/components/search_result.rs deleted file mode 100644 index 325f717..0000000 --- a/src/modules/components/search_result.rs +++ /dev/null @@ -1,55 +0,0 @@ -use sai_backend::utils::prelude::*; - -#[derive(PartialEq, Props, Clone)] -pub struct InternSearchResult { - node: StrongNode, -} - -impl From for InternSearchResult { - fn from(node_context: StrongNode) -> Self { - Self::builder().node(node_context).build() - } -} - -#[sai_macros::element("component")] -pub fn SearchResult(style: String, icons: Icons, intern: InternSearchResult) -> Element { - let dragstart = move |_| *crate::global::context::DRAG_NODE.write() = Some(intern.node.clone()); - - rsx! { - style { {style} } - div { - draggable: true, - class: "SearchResult", - ondragstart: dragstart, - div { - class: "wrapper items", - h3 { span { id: "name", "SampleNode" } } - - div { - class: "wrapper", - div { - class: "wrapper a", - a { id: "website", "www.sne.com" } - } - div { - class: "wrapper i", - - div { id: "open", class: "icon" } - div { id: "favorite", class: "icon" } - } - } - } - h5 { id: "version", "12.23.1" } - p { id: "description", - { - "This is a sample description. Don't take it seriously. It describes a node in need of documentation. - I'll just write more to see how the description looks in html with css..." - } - } - address { - id: "author", - "sertgrc" - } - } - } -} diff --git a/src/modules/global.rs b/src/modules/global.rs deleted file mode 100644 index eed5bfa..0000000 --- a/src/modules/global.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub(crate) mod context { - use dioxus::signals::*; - use sai_backend::utils::prelude::StrongNode; - pub(crate) static DRAG_NODE: GlobalSignal> = Signal::global(|| None); - use crate::components::InternConnection; - pub(crate) static CONNECTION: GlobalSignal> = Signal::global(|| None); -} diff --git a/src/modules/pages/editor.rs b/src/modules/pages/editor.rs deleted file mode 100644 index 467f84f..0000000 --- a/src/modules/pages/editor.rs +++ /dev/null @@ -1,43 +0,0 @@ -#[sai_macros::element("page")] -pub fn Editor(style: String, icons: Icons) -> Element { - use crate::components::*; - - rsx! { - style { { style } } - style { "html {{overflow: hidden;}}"} - main { - Divider - { - section { - Viewport {} - } - aside { - z_index: 2, - nav {} - section { - Search {} - } - } - } - } - } -} - -use crate::platform::Window; -pub fn EditorWindow() -> Window { - #[cfg(feature = "desktop")] - { - Window::new(super::Editor, || { - use dioxus::desktop::{Config, LogicalSize, WindowBuilder}; - Config::default().with_menu(None).with_window( - WindowBuilder::new() - .with_inner_size(LogicalSize::new(1920, 1080)) - .with_transparent(true), - ) - }) - } - #[cfg(feature = "web")] - { - Window::new(NAME) - } -} diff --git a/src/modules/pages/mod.rs b/src/modules/pages/mod.rs deleted file mode 100644 index 075abcf..0000000 --- a/src/modules/pages/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod start; -pub use start::*; -pub mod search; -pub use search::*; -pub mod new; -pub use new::*; -pub mod editor; -pub use editor::*; diff --git a/src/modules/pages/search.rs b/src/modules/pages/search.rs deleted file mode 100644 index 6bd6594..0000000 --- a/src/modules/pages/search.rs +++ /dev/null @@ -1,27 +0,0 @@ -#[sai_macros::element("page")] -pub fn Search(style: String) -> Element { - use crate::components::*; - rsx! { - style { { style } } - main { - Search {} - } - } -} - -use crate::platform::Window; -pub fn SearchWindow() -> Window { - #[cfg(feature = "desktop")] - { - Window::new(super::Search, || { - use dioxus::desktop::{Config, LogicalSize, WindowBuilder}; - Config::default() - .with_menu(None) - .with_window(WindowBuilder::new().with_transparent(true)) - }) - } - #[cfg(feature = "web")] - { - Window::new(NAME) - } -} diff --git a/src/modules/pages/start.rs b/src/modules/pages/start.rs deleted file mode 100644 index 5b03036..0000000 --- a/src/modules/pages/start.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub use crate::platform::*; - -#[sai_macros::element("page")] -pub fn Start(style: String, icons: Icons) -> Element { - let search_icon = icons.get("search").expect("search icon not found").clone(); - let new_icon = icons.get("new").expect("new icon not found").clone(); - let editor_icon = icons.get("editor").expect("editor icon not found").clone(); - - rsx! { - style { { style } } - main { - article { - section { - button { class: "search", onclick: |_| { crate::pages::search::SearchWindow().open(); }, div { class: "icon search", dangerous_inner_html: search_icon } } - button { class: "new", onclick: |_| { crate::pages::new::NewWindow().open(); }, div { class: "icon new", dangerous_inner_html: new_icon } } - button { class: "editor", onclick: |_| { crate::pages::editor::EditorWindow().open(); }, div { class: "icon", id: "editor", dangerous_inner_html: editor_icon } } - } - } - } - } -} - -pub fn StartWindow() -> Window { - #[cfg(feature = "desktop")] - { - Window::new(super::Start, || { - use dioxus::desktop::{Config, LogicalSize, WindowBuilder}; - let size = LogicalSize::new(480, 330); - Config::default().with_menu(None).with_window( - WindowBuilder::default() - .with_max_inner_size(size) - .with_min_inner_size(size) - .with_resizable(true) - .with_maximizable(false) - .with_transparent(true) - .with_title(NAME), - ) - }) - } - #[cfg(feature = "web")] - { - Window::new(NAME) - } -} diff --git a/src/modules/platform.rs b/src/modules/platform.rs deleted file mode 100644 index 209b456..0000000 --- a/src/modules/platform.rs +++ /dev/null @@ -1,15 +0,0 @@ -pub trait WindowLike { - fn open(&self); -} - -#[cfg(feature = "desktop")] -pub mod desktop; - -#[cfg(feature = "desktop")] -pub use desktop::*; - -#[cfg(feature = "web")] -pub mod web; - -#[cfg(feature = "web")] -pub use web::*; diff --git a/src/modules/platform/desktop.rs b/src/modules/platform/desktop.rs deleted file mode 100644 index dd00e38..0000000 --- a/src/modules/platform/desktop.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod window; -pub use window::*; - -pub fn launch() { - use dioxus::desktop::{launch::launch_virtual_dom, Config}; - let start_window = crate::pages::start::StartWindow(); - launch_virtual_dom(start_window.virtual_dom(), start_window.config()); -} diff --git a/src/modules/platform/desktop/macros.rs b/src/modules/platform/desktop/macros.rs deleted file mode 100644 index 14e74d2..0000000 --- a/src/modules/platform/desktop/macros.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[macro_export] -macro_rules! desktop_platform_window { - ($CONFIG:expr) => { - #[cfg(feature = "desktop")] - pub mod platform { - use dioxus::desktop::{Config, LogicalSize, WindowBuilder, WindowCloseBehaviour}; - fn config() -> Config { - $CONFIG - } - - use crate::platform::window::Window; - pub fn window() -> Window { - Window { super::element, config } - } - } - }; -} - diff --git a/src/modules/platform/desktop/window.rs b/src/modules/platform/desktop/window.rs deleted file mode 100644 index d5e74d5..0000000 --- a/src/modules/platform/desktop/window.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::platform::WindowLike; -use dioxus::desktop::Config; -use dioxus::dioxus_core::Element; -use dioxus::prelude::*; -use std::fmt::Debug; - -pub struct Window { - pub(crate) app: fn() -> Element, - pub(crate) config: fn() -> Config, -} - -impl Window { - pub fn new(app: fn() -> Element, config: fn() -> Config) -> Self { - Window { app, config } - } - pub fn config(&self) -> Config { - (&self.config)() - } - pub fn virtual_dom(&self) -> VirtualDom { - VirtualDom::new(self.app) - } -} - -impl WindowLike for Window { - fn open(&self) { - _ = dioxus::desktop::window().new_window(self.virtual_dom(), self.config()) - } -} diff --git a/src/modules/platform/web.rs b/src/modules/platform/web.rs deleted file mode 100644 index a3c459d..0000000 --- a/src/modules/platform/web.rs +++ /dev/null @@ -1,42 +0,0 @@ -pub mod window; -pub use window::*; - -use crate::pages::*; - -use dioxus::prelude::*; - -#[derive(Routable, Clone)] -#[rustfmt::skip] -enum Route { - #[route("/")] - Start {}, - #[route("/search")] - Search {}, - #[route("/new")] - New {}, - #[route("/editor")] - Editor {}, - #[route("/:..route")] - PageNotFound { - route: Vec, - }, -} - -#[component] -fn PageNotFound(route: Vec) -> Element { - rsx! { - h1 { "Page not found" } - p { "We are terribly sorry, but the page you requested doesn't exist." } - pre { color: "red", "log:\nattemped to navigate to: {route:?}" } - } -} - -#[component] -fn App() -> Element { - rsx! { Router:: {} } -} - -pub fn launch() { - use dioxus::web::{launch::launch_virtual_dom, Config}; - launch_virtual_dom(VirtualDom::new(App), Config::default()); -} diff --git a/src/modules/platform/web/macros.rs b/src/modules/platform/web/macros.rs deleted file mode 100644 index 51bf913..0000000 --- a/src/modules/platform/web/macros.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[macro_export] -macro_rules! web_platform_window { - ($CURRENT_PAGE_NAME:expr) => { - pub mod platform { - use super::*; - use crate::platform::window::Window; - - /// Creates a new window for the $CURRENT_PAGE_NAME page. - /// - /// # Returns - /// A `Window` instance configured with the current page name. - pub fn window() -> Window { - // Create a new window using the current page name as the route. - Window::new($CURRENT_PAGE_NAME) - } - } - }; -} - - diff --git a/src/modules/platform/web/window.rs b/src/modules/platform/web/window.rs deleted file mode 100644 index bc009b0..0000000 --- a/src/modules/platform/web/window.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::platform::WindowLike; -use dioxus::dioxus_core::Element; -use dioxus::prelude::*; -use dioxus::web::Config; -use std::fmt::Debug; - -pub struct Window { - pub route: String, -} - -impl Window { - pub fn new(route: &str) -> Self { - Window { - route: route.to_string(), - } - } -} - -impl WindowLike for Window { - fn open(&self) { - _ = router().push(self.route.clone()); - } -}