From 931a1b37e4d2c917a03def0ff03230d0fdb7071d Mon Sep 17 00:00:00 2001 From: = Date: Wed, 18 Jun 2025 14:30:33 +0200 Subject: [PATCH 01/17] icon macro working --- Cargo.toml | 3 +- packages/frontend/Cargo.toml | 1 + packages/frontend/assets/style/main.css | 14 +++ .../frontend/assets/style/pages/start.css | 1 + packages/frontend/src/lib.rs | 24 +++- packages/frontend/src/modules.rs | 6 +- packages/frontend/src/modules/components.rs | 10 ++ .../src/modules/components/connection.rs | 9 +- .../src/modules/components/divider.rs | 6 +- .../src/modules/components/drag_area.rs | 6 +- .../src/modules/components/draggable.rs | 7 +- .../src/modules/components/labeled_box.rs | 6 +- .../src/modules/components/nav_button.rs | 9 +- .../frontend/src/modules/components/node.rs | 9 +- .../src/modules/components/runtime_param.rs | 8 +- .../frontend/src/modules/components/search.rs | 11 +- .../src/modules/components/search_result.rs | 10 +- .../src/modules/components/static_param.rs | 6 +- .../src/modules/components/viewport.rs | 30 ++--- packages/frontend/src/modules/config.rs | 2 - packages/frontend/src/modules/global.rs | 5 - .../frontend/src/modules/global/context.rs | 5 - packages/frontend/src/modules/icons.rs | 11 ++ packages/frontend/src/modules/pages.rs | 10 ++ packages/frontend/src/modules/pages/editor.rs | 6 +- packages/frontend/src/modules/pages/new.rs | 6 +- packages/frontend/src/modules/pages/search.rs | 6 +- packages/frontend/src/modules/pages/start.rs | 36 ++---- packages/macros/Cargo.toml | 7 +- packages/macros/src/icon.rs | 109 ++++++++++++++++++ packages/macros/src/lib.rs | 11 ++ packages/platforms/desktop/src/main.rs | 5 +- 32 files changed, 305 insertions(+), 90 deletions(-) delete mode 100644 packages/frontend/src/modules/config.rs delete mode 100644 packages/frontend/src/modules/global.rs delete mode 100644 packages/frontend/src/modules/global/context.rs create mode 100644 packages/frontend/src/modules/icons.rs create mode 100644 packages/macros/src/icon.rs diff --git a/Cargo.toml b/Cargo.toml index d363a87..4440442 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,4 +30,5 @@ inherits = "dev" [profile.android-dev] inherits = "dev" - +[profile.dev] +overflow-checks = false diff --git a/packages/frontend/Cargo.toml b/packages/frontend/Cargo.toml index 7e5cf3a..3c589fc 100644 --- a/packages/frontend/Cargo.toml +++ b/packages/frontend/Cargo.toml @@ -18,3 +18,4 @@ colored = { version = "3.0.0" } regex = { version = "1.11.1" } derive-new = { version = "0.7.0" } derive_builder = { version = "0.20.2" } +tokio = { version = "1.43.0", features = ["rt"]} diff --git a/packages/frontend/assets/style/main.css b/packages/frontend/assets/style/main.css index e69de29..8b6675d 100644 --- a/packages/frontend/assets/style/main.css +++ b/packages/frontend/assets/style/main.css @@ -0,0 +1,14 @@ +svg { + height: 100%; + width: 100%; +} + +.icon { + width: 2rem; + height: 2rem; +} + +.button { + padding: 1rem; + margin: 1rem; +} diff --git a/packages/frontend/assets/style/pages/start.css b/packages/frontend/assets/style/pages/start.css index e69de29..bd0041a 100644 --- a/packages/frontend/assets/style/pages/start.css +++ b/packages/frontend/assets/style/pages/start.css @@ -0,0 +1 @@ +@import "../main.css"; diff --git a/packages/frontend/src/lib.rs b/packages/frontend/src/lib.rs index cff0af7..c6caddf 100644 --- a/packages/frontend/src/lib.rs +++ b/packages/frontend/src/lib.rs @@ -1,11 +1,23 @@ +// %%% lib.rs %%% + +// %% global exports %% pub mod modules; +// %% global prelude %% 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}; +} + +// %% global utils %% +pub(crate) mod utils { + // % dioxus % + pub use dioxus::html::geometry::{euclid::Vector2D, *}; + pub use dioxus::logger::tracing::*; + pub use dioxus::prelude::*; + // % macros % + pub use simple_ai_macros::*; + // % icons % + pub use super::modules::icons::*; + // % custom types % + pub type PageVector = Vector2D; } diff --git a/packages/frontend/src/modules.rs b/packages/frontend/src/modules.rs index 7ae84f4..0514776 100644 --- a/packages/frontend/src/modules.rs +++ b/packages/frontend/src/modules.rs @@ -1,4 +1,6 @@ +// %%% modules.rs %%% + +// %% exports %% pub mod components; -pub mod config; -pub mod global; +pub mod icons; pub mod pages; diff --git a/packages/frontend/src/modules/components.rs b/packages/frontend/src/modules/components.rs index a671c6d..199bfd8 100644 --- a/packages/frontend/src/modules/components.rs +++ b/packages/frontend/src/modules/components.rs @@ -1,3 +1,6 @@ +// %%% components.rs %%% + +// %% exports %% pub mod connection; pub mod divider; pub mod drag_area; @@ -11,6 +14,7 @@ pub mod search_result; pub mod static_param; pub mod viewport; +// %% prelude %% pub mod prelude { pub use super::divider::*; pub use super::drag_area::*; @@ -27,3 +31,9 @@ pub mod prelude { pub use super::super::static_param::*; } } + +// %% utils %% +pub(crate) mod utils { + pub use crate::utils::*; + pub use simple_ai_backend::utils::prelude::*; +} diff --git a/packages/frontend/src/modules/components/connection.rs b/packages/frontend/src/modules/components/connection.rs index 1aa1d71..651b1f0 100644 --- a/packages/frontend/src/modules/components/connection.rs +++ b/packages/frontend/src/modules/components/connection.rs @@ -1,6 +1,9 @@ -use crate::prelude::*; -use simple_ai_backend::utils::prelude::*; +// %%% components / connection.rs %%% +// %% includes %% +use super::utils::*; + +// %% main %% #[derive(PartialEq, Props, Clone)] pub struct InternConnection { pub kind: RuntimeParamKind, @@ -81,7 +84,7 @@ pub fn Connection(intern: InternConnection) -> Element { }; let div_mouseup = move |e: MouseEvent| { - *CONNECTION.write() = Some(intern.clone()); + // *CONNECTION.write() = Some(intern.clone()); }; let div_mounted = move |e: MountedEvent| async move { diff --git a/packages/frontend/src/modules/components/divider.rs b/packages/frontend/src/modules/components/divider.rs index 81a800c..c92eea2 100644 --- a/packages/frontend/src/modules/components/divider.rs +++ b/packages/frontend/src/modules/components/divider.rs @@ -1,5 +1,9 @@ -use crate::prelude::*; +// %%% components / divider.rs %%% +// %% includes %% +use super::utils::*; + +// %% main %% #[component] pub fn Divider(children: Element) -> Element { let script = r#####" diff --git a/packages/frontend/src/modules/components/drag_area.rs b/packages/frontend/src/modules/components/drag_area.rs index cc72f42..2774464 100644 --- a/packages/frontend/src/modules/components/drag_area.rs +++ b/packages/frontend/src/modules/components/drag_area.rs @@ -1,5 +1,9 @@ -use crate::prelude::*; +// %%% components / rag_area.rs %%% +// %% includes %% +use super::utils::*; + +// %% main %% #[derive(Default, Clone, Copy)] pub struct DragContext { pub cursor_start_position: Signal, diff --git a/packages/frontend/src/modules/components/draggable.rs b/packages/frontend/src/modules/components/draggable.rs index 995dd74..75d5485 100644 --- a/packages/frontend/src/modules/components/draggable.rs +++ b/packages/frontend/src/modules/components/draggable.rs @@ -1,5 +1,10 @@ -use crate::prelude::{components::prelude::*, *}; +// %%% components / draggable.rs %%% +// %% includes %% +use super::drag_area::DragContext; +use super::utils::*; + +// %% main %% #[component] pub fn Draggable( #[props(default)] ondraggingstart: Callback, diff --git a/packages/frontend/src/modules/components/labeled_box.rs b/packages/frontend/src/modules/components/labeled_box.rs index 4c9770f..79bed93 100644 --- a/packages/frontend/src/modules/components/labeled_box.rs +++ b/packages/frontend/src/modules/components/labeled_box.rs @@ -1,5 +1,9 @@ -use crate::prelude::*; +// %%% components / labeled_box.rs %%% +// %% includes %% +use super::utils::*; + +// %% main %% #[component] pub fn LabeledBox(children: Element) -> Element { let script = r#####" diff --git a/packages/frontend/src/modules/components/nav_button.rs b/packages/frontend/src/modules/components/nav_button.rs index 9a714a6..e028952 100644 --- a/packages/frontend/src/modules/components/nav_button.rs +++ b/packages/frontend/src/modules/components/nav_button.rs @@ -1,6 +1,11 @@ -use crate::prelude::*; +// %%% components / nav_button.rs %%% + +// %% includes %% +use super::utils::*; use dioxus::router::prelude::*; +// %% main %% + #[component] pub fn NavButton( children: Element, @@ -10,7 +15,7 @@ pub fn NavButton( let class_unw = class.unwrap_or_default(); rsx! { Link { - class: "NavbarButton {class_unw}", + class: "NavbarButton button {class_unw}", to: to, div { {children} diff --git a/packages/frontend/src/modules/components/node.rs b/packages/frontend/src/modules/components/node.rs index f38c470..beae957 100644 --- a/packages/frontend/src/modules/components/node.rs +++ b/packages/frontend/src/modules/components/node.rs @@ -1,6 +1,11 @@ -use crate::prelude::{components::prelude::params::*, *}; -use simple_ai_backend::utils::prelude::*; +// %%% components / node.rs %%% +// %% includes %% +use super::runtime_param::{InternRuntimeParam, RuntimeParam}; +use super::static_param::{InternStaticParam, StaticParam}; +use super::utils::*; + +// %% main %% #[derive(PartialEq, Props, Clone)] pub struct InternNode { pub node: StrongNode, diff --git a/packages/frontend/src/modules/components/runtime_param.rs b/packages/frontend/src/modules/components/runtime_param.rs index 2ac7a33..0c4c67d 100644 --- a/packages/frontend/src/modules/components/runtime_param.rs +++ b/packages/frontend/src/modules/components/runtime_param.rs @@ -1,6 +1,10 @@ -use crate::prelude::{components::prelude::params::*, *}; -use simple_ai_backend::utils::prelude::*; +// %%% components / runtime_param.rs %%% +// %% includes %% +use super::connection::{Connection, InternConnection}; +use super::utils::*; + +// %% main %% #[derive(PartialEq, Props, Clone)] pub struct InternRuntimeParam { pub param: StrongParam, diff --git a/packages/frontend/src/modules/components/search.rs b/packages/frontend/src/modules/components/search.rs index 48e1039..bcc0539 100644 --- a/packages/frontend/src/modules/components/search.rs +++ b/packages/frontend/src/modules/components/search.rs @@ -1,9 +1,10 @@ -use crate::prelude::{ - components::prelude::{InternSearchResult, SearchResult}, - *, -}; -use simple_ai_backend::utils::prelude::*; +// %%% components / search.rs %%% +// %% includes %% +use super::search_result::{InternSearchResult, SearchResult}; +use super::utils::*; + +// %% main %% #[component] pub fn Search() -> Element { let mut intern_search_results = use_signal(Vec::::new); diff --git a/packages/frontend/src/modules/components/search_result.rs b/packages/frontend/src/modules/components/search_result.rs index 8269ab0..244a865 100644 --- a/packages/frontend/src/modules/components/search_result.rs +++ b/packages/frontend/src/modules/components/search_result.rs @@ -1,6 +1,10 @@ -use crate::prelude::{components::prelude::Draggable, *}; -use simple_ai_backend::utils::prelude::*; +// %%% components / search_result.rs %%% +// %% includes %% +use super::draggable::Draggable; +use super::utils::*; + +// %% main %% #[derive(PartialEq, Props, Clone, Copy)] pub struct InternSearchResult { pub node: Signal, @@ -17,7 +21,7 @@ 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); + // *DRAG_NODE.write() = Some(node); }; rsx! { diff --git a/packages/frontend/src/modules/components/static_param.rs b/packages/frontend/src/modules/components/static_param.rs index 3f375d2..58cb5a5 100644 --- a/packages/frontend/src/modules/components/static_param.rs +++ b/packages/frontend/src/modules/components/static_param.rs @@ -1,6 +1,10 @@ -use crate::prelude::*; +// %%% components / static_param.rs %%% + +// %% includes %% +use super::utils::*; use simple_ai_backend::utils::prelude::*; +// %% main %% #[derive(PartialEq, Props, Clone)] pub struct InternStaticParam { pub param: StrongParam, diff --git a/packages/frontend/src/modules/components/viewport.rs b/packages/frontend/src/modules/components/viewport.rs index 3106f46..264baeb 100644 --- a/packages/frontend/src/modules/components/viewport.rs +++ b/packages/frontend/src/modules/components/viewport.rs @@ -1,6 +1,10 @@ -use crate::prelude::*; +// %%% components / viewport.rs %%% + +// %% includes %% +use super::utils::*; use simple_ai_backend::utils::prelude::*; +// %% main %% #[derive(Clone)] pub struct ViewportNodeContainer { pub backend_node_container: NodeContainer, @@ -72,15 +76,15 @@ pub fn Viewport( }; 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)); - } + // 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 ------------------------------ // @@ -130,9 +134,9 @@ pub fn Viewport( if let Some(mut connection) = pressed_connection() { connection.dimensions.set(get_diff(&e)); connection.pressed.set(false); - if let Some(mut c) = CONNECTION() { - c.foreign_dimensions.set((connection.dimensions)()); - } + // if let Some(mut c) = CONNECTION() { + // c.foreign_dimensions.set((connection.dimensions)()); + // } pressed_connection.set(None); } else if let Some(mut node) = pressed_node() { node.cursor.set("grab".into()); diff --git a/packages/frontend/src/modules/config.rs b/packages/frontend/src/modules/config.rs deleted file mode 100644 index 389b667..0000000 --- a/packages/frontend/src/modules/config.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub static DEFAULT_ICONS: &str = "con1"; -pub static DEFAULT_STYLES: &str = "cybr1"; diff --git a/packages/frontend/src/modules/global.rs b/packages/frontend/src/modules/global.rs deleted file mode 100644 index 5a2c63e..0000000 --- a/packages/frontend/src/modules/global.rs +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index d5a6628..0000000 --- a/packages/frontend/src/modules/global/context.rs +++ /dev/null @@ -1,5 +0,0 @@ -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/icons.rs b/packages/frontend/src/modules/icons.rs new file mode 100644 index 0000000..28e5e92 --- /dev/null +++ b/packages/frontend/src/modules/icons.rs @@ -0,0 +1,11 @@ +// %%% icons.rs %%% + +// %% includes %% +use crate::utils::*; + +// %% main %% +icon! { + search: , + new: , + editor: +} diff --git a/packages/frontend/src/modules/pages.rs b/packages/frontend/src/modules/pages.rs index 36808da..b082465 100644 --- a/packages/frontend/src/modules/pages.rs +++ b/packages/frontend/src/modules/pages.rs @@ -1,11 +1,21 @@ +// %%% pages.rs %%% + +// %% exports %% pub mod editor; pub mod new; pub mod search; pub mod start; +// %% prelude %% pub mod prelude { pub use super::editor::Editor; pub use super::new::New; pub use super::search::Search; pub use super::start::Start; } + +// %% utils %% +pub(crate) mod utils { + pub use crate::modules::components::prelude::*; + pub use crate::utils::*; +} diff --git a/packages/frontend/src/modules/pages/editor.rs b/packages/frontend/src/modules/pages/editor.rs index 3583d5a..3eb03fc 100644 --- a/packages/frontend/src/modules/pages/editor.rs +++ b/packages/frontend/src/modules/pages/editor.rs @@ -1,5 +1,9 @@ -use crate::prelude::{components::prelude::*, *}; +// %%% pages / editor.rs %%% +// %% includes %% +use super::utils::*; + +// %% main %% #[page] pub fn Editor() -> Element { rsx! { diff --git a/packages/frontend/src/modules/pages/new.rs b/packages/frontend/src/modules/pages/new.rs index cc72d57..9f140bb 100644 --- a/packages/frontend/src/modules/pages/new.rs +++ b/packages/frontend/src/modules/pages/new.rs @@ -1,5 +1,9 @@ -use crate::prelude::{components::prelude::*, *}; +// %%% pages / new.rs %%% +// %% includes %% +use super::utils::*; + +// %% main %% #[page] pub fn New() -> Element { rsx! { diff --git a/packages/frontend/src/modules/pages/search.rs b/packages/frontend/src/modules/pages/search.rs index e34bfc0..f5cbefe 100644 --- a/packages/frontend/src/modules/pages/search.rs +++ b/packages/frontend/src/modules/pages/search.rs @@ -1,5 +1,9 @@ -use crate::prelude::{components::prelude::*, *}; +// %%% pages / search.rs %%% +// %% includes %% +use super::utils::*; + +// %% main %% #[page] pub fn Search() -> Element { rsx! { diff --git a/packages/frontend/src/modules/pages/start.rs b/packages/frontend/src/modules/pages/start.rs index b16190e..cf77ddd 100644 --- a/packages/frontend/src/modules/pages/start.rs +++ b/packages/frontend/src/modules/pages/start.rs @@ -1,6 +1,10 @@ -pub use crate::prelude::{components::prelude::*, *}; +// %%% pages / start.rs %%% + +// %% includes %% +use super::utils::*; use dioxus::router::prelude::*; +// %% main %% #[page] pub fn Start( #[props(into)] search_route: NavigationTarget, @@ -11,33 +15,9 @@ pub fn Start( 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" } - } - - } } + NavButton { class: "search", to: search_route, SearchIcon {}} + NavButton { class: "new", to: new_route, NewIcon {} } + NavButton { class: "editor", to: editor_route, EditorIcon {} } } } } diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml index 39bc7e7..3b958bc 100644 --- a/packages/macros/Cargo.toml +++ b/packages/macros/Cargo.toml @@ -1,13 +1,16 @@ [package] name = "simple-ai-macros" version = { workspace = true } -edition = "2021" +edition = "2024" [lib] proc-macro = true [dependencies] -proc-macro2 = { version = "1" } +proc-macro2 = { version = "1.0" } syn = { version = "2.0", features = ["full", "printing", "parsing"] } quote = { version = "1.0" } change-case = { version = "0.2.0" } +dioxus-rsx-rosetta = { version = "0.6" } +dioxus-autofmt = { version = "0.6" } +rstml = { version = "0.12" } diff --git a/packages/macros/src/icon.rs b/packages/macros/src/icon.rs new file mode 100644 index 0000000..8ab2f69 --- /dev/null +++ b/packages/macros/src/icon.rs @@ -0,0 +1,109 @@ +// %%% icon.rs %%% + +// %% includes %% +use change_case::*; +use dioxus_autofmt::write_block_out; +use dioxus_rsx_rosetta::{rsx_from_html, Dom}; +use proc_macro2::{Span, TokenStream}; +use quote::*; +use rstml::node::{Node, NodeAttribute, NodeElement, NodeName, NodeText}; +use std::borrow::Cow; +use std::str::FromStr; +use syn::{ + parse::{Parse, ParseStream}, + parse2, + punctuated::Punctuated, + Ident, Lit, Token, +}; + +// %% main %% + +// % Field % +struct Field { + member: Ident, + svg: String, + colon: Token![:], +} + +impl Parse for Field { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + member: input.parse()?, + colon: input.parse()?, + svg: { + let mut value = String::new(); + let mut needs_space = false; + while !input.is_empty() && !input.peek(Token![,]) { + let mut p; + if let Ok(ident) = input.parse::() { + p = ident.to_string(); + if needs_space { + p.insert(0, ' '); + } + needs_space = true; + } else if let Ok(lit) = input.parse::() { + p = match lit { + Lit::Str(s) => format!("\"{}\"", s.value()), + Lit::Int(i) => i.to_string(), + Lit::Char(c) => format!("'{}'", c.value()), + Lit::Bool(b) => b.value().to_string(), + _ => String::new(), + }; + needs_space = true; + } else { + p = input.parse::().unwrap().to_string(); + needs_space = false; + // println!("token: [{p}]"); + } + value.push_str(&p); + } + println!("value {}", value); + value + }, + }) + } +} + +// % FieldValues % +struct FieldValues { + fields: Punctuated, +} + +impl Parse for FieldValues { + fn parse(input: ParseStream) -> syn::Result { + Ok(FieldValues { + fields: input.parse_terminated(Field::parse, Token![,])?, + }) + } +} + +// % macro implementation % +pub fn macro_impl(item: TokenStream) -> TokenStream { + let fields: FieldValues = parse2(item).unwrap(); + let mut out: TokenStream = quote! { + use dioxus::prelude::*; + } + .into(); + for field in fields.fields { + let function_name = pascal_case(&format!("{}Icon", field.member)); + let function_ident = Ident::new(&function_name, Span::call_site()); + let svg_dom = Dom::parse(&field.svg).expect("failed parsing dom"); + let svg_body = rsx_from_html(&svg_dom); + let block = write_block_out(&svg_body).expect("failed writing block"); + let svg = TokenStream::from_str(&block).unwrap(); + let classes = format!("Icon {}", function_name); + println!("block: {}", block); + quote! { + pub fn #function_ident() -> Element { + rsx! { + div { + class: #classes, + #svg + } + } + } + } + .to_tokens(&mut out); + } + out +} diff --git a/packages/macros/src/lib.rs b/packages/macros/src/lib.rs index 239a657..4e0856b 100644 --- a/packages/macros/src/lib.rs +++ b/packages/macros/src/lib.rs @@ -13,5 +13,16 @@ macro_rules! create_attr_macro { }; } +macro_rules! create_function_style_macro { + ($name: ident) => { + mod $name; + #[proc_macro] + pub fn $name(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + $name::macro_impl(syn::parse_macro_input!(item)).into() + } + }; +} + create_attr_macro!(component); create_attr_macro!(page); +create_function_style_macro!(icon); diff --git a/packages/platforms/desktop/src/main.rs b/packages/platforms/desktop/src/main.rs index 4e7a855..c16a52d 100644 --- a/packages/platforms/desktop/src/main.rs +++ b/packages/platforms/desktop/src/main.rs @@ -13,6 +13,9 @@ fn main() { dioxus::launch(App); } +#[component] fn App() -> Element { - rsx! { Router:: {} } + rsx! { + Router:: {} + } } From 407fe1b2a29a468dd89497dcfcf0c1ecc57711b2 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 20 Jun 2025 20:03:39 +0200 Subject: [PATCH 02/17] macro fix --- packages/frontend/assets/style/main.css | 2 +- .../frontend/assets/style/pages/start.css | 1 - packages/macros/src/element.rs | 55 +++++++++++++------ packages/macros/src/icon.rs | 2 - 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/packages/frontend/assets/style/main.css b/packages/frontend/assets/style/main.css index 8b6675d..4c54a96 100644 --- a/packages/frontend/assets/style/main.css +++ b/packages/frontend/assets/style/main.css @@ -3,7 +3,7 @@ svg { width: 100%; } -.icon { +.Icon { width: 2rem; height: 2rem; } diff --git a/packages/frontend/assets/style/pages/start.css b/packages/frontend/assets/style/pages/start.css index bd0041a..e69de29 100644 --- a/packages/frontend/assets/style/pages/start.css +++ b/packages/frontend/assets/style/pages/start.css @@ -1 +0,0 @@ -@import "../main.css"; diff --git a/packages/macros/src/element.rs b/packages/macros/src/element.rs index e669d6f..6dfa522 100644 --- a/packages/macros/src/element.rs +++ b/packages/macros/src/element.rs @@ -13,40 +13,61 @@ pub fn element(item: TokenStream, str_type_name: &str) -> TokenStream { 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") + let style_asset_path = std::path::PathBuf::from("/assets").join("style"); + + let pathbuf_asset_path = style_asset_path .join(str_type_name) .join(str_lowercase_emt_name + ".css"); + let pathbuf_main_css_path = style_asset_path.join("main.css"); + let asset_path = LitStr::new( - str_asset_path.to_str().unwrap_or_default(), + pathbuf_asset_path.to_str().unwrap_or_default(), Span::call_site(), ); + let main_css_path = LitStr::new( + pathbuf_main_css_path.to_str().unwrap_or_default(), + Span::call_site(), + ); + + let mut main_css_link = quote! {}; + let mut main_css_asset = quote! {}; + if str_type_name == "pages" { + main_css_link = quote! { + document::Link { rel: "stylesheet", href: MAIN_CSS }, + }; + main_css_asset = quote! { + use dioxus::prelude::{asset, manganis, Asset}; + const MAIN_CSS: Asset = asset!(#main_css_path); + }; + } for stmt in &mut out_items.block.stmts { - if let Stmt::Expr(syn::Expr::Macro(macro_expr), _) = stmt { + if let Stmt::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() + let mut new_tokens = TokenStream::new(); + quote! { + document::Link { rel: "stylesheet", href: #css_emt_name }, + #main_css_link + } + .to_tokens(&mut new_tokens); + rsx_tokens.to_tokens(&mut new_tokens); + new_tokens }; } } } - quote! { + let out = quote! { + #main_css_asset const #css_emt_name: Asset = asset!(#asset_path); #[dioxus::core_macro::component] #out_items - } + }; + + println!("out quote: {}", out.to_string()); + + out } diff --git a/packages/macros/src/icon.rs b/packages/macros/src/icon.rs index 8ab2f69..79268cc 100644 --- a/packages/macros/src/icon.rs +++ b/packages/macros/src/icon.rs @@ -57,7 +57,6 @@ impl Parse for Field { } value.push_str(&p); } - println!("value {}", value); value }, }) @@ -92,7 +91,6 @@ pub fn macro_impl(item: TokenStream) -> TokenStream { let block = write_block_out(&svg_body).expect("failed writing block"); let svg = TokenStream::from_str(&block).unwrap(); let classes = format!("Icon {}", function_name); - println!("block: {}", block); quote! { pub fn #function_ident() -> Element { rsx! { From f757b16a3eda00050d40bbdfe700c7bb4bf28df4 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 28 Sep 2025 18:06:13 +0200 Subject: [PATCH 03/17] work save --- Cargo.toml | 4 +- packages/backend/src/lib.rs | 6 +- packages/backend/src/modules/nms.rs | 2 + packages/backend/src/modules/nms/create.rs | 92 +++--- packages/backend/src/modules/nms/delete.rs | 2 +- packages/backend/src/modules/nms/query.rs | 4 +- packages/backend/src/modules/nms/save.rs | 4 +- packages/backend/src/modules/utils.rs | 8 +- packages/backend/src/modules/utils/node.rs | 5 +- .../backend/src/modules/utils/query_filter.rs | 2 +- .../backend/src/modules/utils/save_node.rs | 2 +- packages/backend/src/modules/utils/search.rs | 28 -- packages/frontend/Cargo.toml | 25 +- packages/frontend/Elements.toml | 1 + .../frontend/assets/style/components/node.css | 0 .../assets/style/components/search.css | 0 .../assets/style/components/search_result.css | 0 .../assets/style/components/viewport.css | 0 packages/frontend/assets/style/entry.css | 187 +++++++++++ .../{components => items}/connection.css | 0 .../frontend/assets/style/items/divider.css | 25 ++ .../style/{components => items}/drag_area.css | 0 .../style/{components => items}/draggable.css | 0 .../divider.css => items/focus_button.css} | 0 .../{components => items}/labeled_box.css | 0 .../{components => items}/nav_button.css | 0 packages/frontend/assets/style/items/node.css | 7 + .../{components => items}/runtime_param.css | 0 .../frontend/assets/style/items/search.css | 24 ++ .../assets/style/items/search_result.css | 17 + .../{components => items}/static_param.css | 0 .../frontend/assets/style/items/viewport.css | 3 + packages/frontend/assets/style/main.css | 14 - .../frontend/assets/style/pages/editor.css | 3 + .../frontend/assets/style/pages/search.css | 2 + .../frontend/assets/style/pages/start.css | 19 ++ packages/frontend/src/modules.rs | 1 + packages/frontend/src/modules/components.rs | 9 +- .../src/modules/components/connection.rs | 2 +- .../src/modules/components/divider.rs | 30 +- .../src/modules/components/drag_area.rs | 21 +- .../src/modules/components/draggable.rs | 130 +++++--- .../src/modules/components/focus_button.rs | 33 ++ .../modules/components/focus_button_array.rs | 56 ++++ .../src/modules/components/labeled_box.rs | 7 +- .../src/modules/components/nav_button.rs | 15 +- .../frontend/src/modules/components/node.rs | 5 + .../frontend/src/modules/components/search.rs | 45 ++- .../src/modules/components/search_result.rs | 9 +- .../src/modules/components/section_toggle.rs | 44 +++ .../src/modules/components/static_param.rs | 2 +- .../src/modules/components/viewport.rs | 26 +- packages/frontend/src/modules/icons.rs | 5 +- packages/frontend/src/modules/pages/editor.rs | 35 ++- packages/frontend/src/modules/pages/new.rs | 55 +++- packages/frontend/src/modules/pages/search.rs | 4 +- packages/frontend/src/modules/pages/start.rs | 19 +- packages/frontend/src/modules/utils.rs | 8 + packages/macros/Cargo.toml | 7 + packages/macros/src/component.rs | 6 - packages/macros/src/element.rs | 90 ++---- packages/macros/src/element/attrs.rs | 89 ++++++ packages/macros/src/element/function.rs | 66 ++++ packages/macros/src/element/handler.rs | 114 +++++++ packages/macros/src/element/kind.rs | 56 ++++ packages/macros/src/element/manifest.rs | 50 +++ packages/macros/src/element/rsx_ast.rs | 67 ++++ packages/macros/src/entry.rs | 14 + packages/macros/src/icon.rs | 7 +- packages/macros/src/item.rs | 14 + packages/macros/src/lib.rs | 66 ++-- packages/macros/src/page.rs | 16 +- packages/macros/src/tests.rs | 4 + .../macros/tests/element/element.expanded.rs | 294 ++++++++++++++++++ packages/macros/tests/element/element.rs | 26 ++ .../macros/tests/element/entry.expanded.rs | 148 +++++++++ packages/macros/tests/element/entry.rs | 10 + .../macros/tests/element/item.expanded.rs | 148 +++++++++ packages/macros/tests/element/item.rs | 10 + .../tests/element/item_dx_element.expanded.rs | 2 + .../macros/tests/element/item_dx_element.rs | 9 + .../macros/tests/element/page.expanded.rs | 148 +++++++++ packages/macros/tests/element/page.rs | 10 + packages/platforms/desktop/Cargo.toml | 1 + packages/platforms/desktop/src/router.rs | 13 +- 85 files changed, 2155 insertions(+), 377 deletions(-) delete mode 100644 packages/backend/src/modules/utils/search.rs create mode 100644 packages/frontend/Elements.toml delete mode 100644 packages/frontend/assets/style/components/node.css delete mode 100644 packages/frontend/assets/style/components/search.css delete mode 100644 packages/frontend/assets/style/components/search_result.css delete mode 100644 packages/frontend/assets/style/components/viewport.css create mode 100644 packages/frontend/assets/style/entry.css rename packages/frontend/assets/style/{components => items}/connection.css (100%) create mode 100644 packages/frontend/assets/style/items/divider.css rename packages/frontend/assets/style/{components => items}/drag_area.css (100%) rename packages/frontend/assets/style/{components => items}/draggable.css (100%) rename packages/frontend/assets/style/{components/divider.css => items/focus_button.css} (100%) rename packages/frontend/assets/style/{components => items}/labeled_box.css (100%) rename packages/frontend/assets/style/{components => items}/nav_button.css (100%) create mode 100644 packages/frontend/assets/style/items/node.css rename packages/frontend/assets/style/{components => items}/runtime_param.css (100%) create mode 100644 packages/frontend/assets/style/items/search.css create mode 100644 packages/frontend/assets/style/items/search_result.css rename packages/frontend/assets/style/{components => items}/static_param.css (100%) create mode 100644 packages/frontend/assets/style/items/viewport.css delete mode 100644 packages/frontend/assets/style/main.css create mode 100644 packages/frontend/src/modules/components/focus_button.rs create mode 100644 packages/frontend/src/modules/components/focus_button_array.rs create mode 100644 packages/frontend/src/modules/components/section_toggle.rs create mode 100644 packages/frontend/src/modules/utils.rs delete mode 100644 packages/macros/src/component.rs create mode 100644 packages/macros/src/element/attrs.rs create mode 100644 packages/macros/src/element/function.rs create mode 100644 packages/macros/src/element/handler.rs create mode 100644 packages/macros/src/element/kind.rs create mode 100644 packages/macros/src/element/manifest.rs create mode 100644 packages/macros/src/element/rsx_ast.rs create mode 100644 packages/macros/src/entry.rs create mode 100644 packages/macros/src/item.rs create mode 100644 packages/macros/src/tests.rs create mode 100644 packages/macros/tests/element/element.expanded.rs create mode 100644 packages/macros/tests/element/element.rs create mode 100644 packages/macros/tests/element/entry.expanded.rs create mode 100644 packages/macros/tests/element/entry.rs create mode 100644 packages/macros/tests/element/item.expanded.rs create mode 100644 packages/macros/tests/element/item.rs create mode 100644 packages/macros/tests/element/item_dx_element.expanded.rs create mode 100644 packages/macros/tests/element/item_dx_element.rs create mode 100644 packages/macros/tests/element/page.expanded.rs create mode 100644 packages/macros/tests/element/page.rs diff --git a/Cargo.toml b/Cargo.toml index 4440442..44f051e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,14 @@ members = [ ] [workspace.package] -version = "0.6.3" +version = "0.0.1" [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" } +dioxus = { version = "0.7.0-rc.0" } [profile] diff --git a/packages/backend/src/lib.rs b/packages/backend/src/lib.rs index 0f20dbc..ba64358 100644 --- a/packages/backend/src/lib.rs +++ b/packages/backend/src/lib.rs @@ -1,2 +1,6 @@ pub mod modules; -pub use modules::*; + +pub mod prelude { + pub use super::modules::nms::*; + pub use super::modules::utils::prelude::*; +} diff --git a/packages/backend/src/modules/nms.rs b/packages/backend/src/modules/nms.rs index e6cc05a..7ef94ba 100644 --- a/packages/backend/src/modules/nms.rs +++ b/packages/backend/src/modules/nms.rs @@ -1,4 +1,6 @@ +pub mod create; pub mod delete; +pub mod modify; pub mod query; pub mod save; diff --git a/packages/backend/src/modules/nms/create.rs b/packages/backend/src/modules/nms/create.rs index 5d70ed5..17b32be 100644 --- a/packages/backend/src/modules/nms/create.rs +++ b/packages/backend/src/modules/nms/create.rs @@ -1,52 +1,52 @@ -use crate::nms::check_name; -use crate::utils::prelude::{save::*, *}; +use crate::modules::nms::check_name; +use crate::modules::utils::prelude::*; 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(()) -} +// 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 meta.; +// 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/packages/backend/src/modules/nms/delete.rs b/packages/backend/src/modules/nms/delete.rs index 2e53324..a924d71 100644 --- a/packages/backend/src/modules/nms/delete.rs +++ b/packages/backend/src/modules/nms/delete.rs @@ -2,7 +2,7 @@ use std::fs::{self, File}; use std::io::Write; use std::path::Path; -use crate::utils::prelude::*; +use crate::modules::utils::prelude::*; pub fn delete_node(name: String, version: Option) -> Result<(), String> { let node_path = Path::new("nodes/").join(&name); diff --git a/packages/backend/src/modules/nms/query.rs b/packages/backend/src/modules/nms/query.rs index 7a65bf3..c796d07 100644 --- a/packages/backend/src/modules/nms/query.rs +++ b/packages/backend/src/modules/nms/query.rs @@ -1,6 +1,6 @@ use std::path::Path; -use crate::utils::prelude::{save::*, *}; +use crate::modules::utils::prelude::*; use walkdir::WalkDir; pub fn get_all_nodes() -> Result { @@ -31,7 +31,7 @@ pub fn get_all_nodes() -> Result { } /// This function searches through all available Nodes and returns a NodeContainer containing all Nodes available for the inferred environment. -pub fn query(query_filter: Vec) -> NodeContainer { +pub fn query_nodes(query_filter: Vec) -> NodeContainer { let all_nodes = get_all_nodes().expect("Error walking directory!"); all_nodes diff --git a/packages/backend/src/modules/nms/save.rs b/packages/backend/src/modules/nms/save.rs index 5d5a398..a7fca44 100644 --- a/packages/backend/src/modules/nms/save.rs +++ b/packages/backend/src/modules/nms/save.rs @@ -1,5 +1,5 @@ -use crate::nms::check_name; -use crate::utils::prelude::{save::*, *}; +use crate::modules::nms::check_name; +use crate::modules::utils::prelude::*; use anyhow::Result; use std::fs::{self, create_dir, create_dir_all, File}; use std::io::Write; diff --git a/packages/backend/src/modules/utils.rs b/packages/backend/src/modules/utils.rs index f22a96e..ac3899c 100644 --- a/packages/backend/src/modules/utils.rs +++ b/packages/backend/src/modules/utils.rs @@ -9,7 +9,6 @@ pub mod param; pub mod query_filter; pub mod save_node; pub mod save_param; -pub mod search; pub mod prelude { pub use super::container::*; @@ -21,9 +20,6 @@ pub mod prelude { pub use super::node::*; pub use super::param::*; pub use super::query_filter::*; - pub use super::search::*; - pub mod save { - pub use super::super::save_node::*; - pub use super::super::save_param::*; - } + pub use super::save_node::*; + pub use super::save_param::*; } diff --git a/packages/backend/src/modules/utils/node.rs b/packages/backend/src/modules/utils/node.rs index 3a8d05a..4a542ab 100644 --- a/packages/backend/src/modules/utils/node.rs +++ b/packages/backend/src/modules/utils/node.rs @@ -1,4 +1,4 @@ -use super::prelude::{save::*, *}; +use super::prelude::*; use derive_builder::Builder; use std::collections::HashMap; // -------------------- NODE KIND -------------------- // @@ -7,19 +7,20 @@ pub enum NodeKind { Code { code: String }, Bundled { bundle: NodeContainer }, } - // -------------------- NODE -------------------- // pub type StrongNode = StrongContext; pub type WeakNode = WeakContext; #[derive(Builder, Clone, PartialEq)] pub struct Node { + #[builder(setter(into))] pub name: String, pub params: Vec, pub version: Version, pub kind: NodeKind, pub description: String, pub author: String, + #[builder(default)] pub compiled: Option, // or and bytes... pub date: Date, #[builder(default)] diff --git a/packages/backend/src/modules/utils/query_filter.rs b/packages/backend/src/modules/utils/query_filter.rs index 71cd99c..05ea1ba 100644 --- a/packages/backend/src/modules/utils/query_filter.rs +++ b/packages/backend/src/modules/utils/query_filter.rs @@ -1,4 +1,4 @@ -use crate::utils::prelude::*; +use crate::modules::utils::prelude::*; use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; // ---------------- QUERY FILTER ---------------- // diff --git a/packages/backend/src/modules/utils/save_node.rs b/packages/backend/src/modules/utils/save_node.rs index 8ec1833..c93a890 100644 --- a/packages/backend/src/modules/utils/save_node.rs +++ b/packages/backend/src/modules/utils/save_node.rs @@ -1,4 +1,4 @@ -use super::prelude::{save::*, *}; +use super::prelude::*; use derive_builder::Builder; use serde::{Deserialize, Serialize}; // -------------------- SAVE NODES -------------------- // diff --git a/packages/backend/src/modules/utils/search.rs b/packages/backend/src/modules/utils/search.rs deleted file mode 100644 index 398223f..0000000 --- a/packages/backend/src/modules/utils/search.rs +++ /dev/null @@ -1,28 +0,0 @@ -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/packages/frontend/Cargo.toml b/packages/frontend/Cargo.toml index 3c589fc..2ff1c33 100644 --- a/packages/frontend/Cargo.toml +++ b/packages/frontend/Cargo.toml @@ -7,15 +7,16 @@ edition = "2021" 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" } -tokio = { version = "1.43.0", features = ["rt"]} +async-recursion = { version = "1.1" } +futures = "0.3" +reqwest = { version = "0.12", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +toml = { version = "0.9" } +anyhow = { version = "1.0" } +colored = { version = "3.0" } +regex = { version = "1.11" } +derive-new = { version = "0.7" } +derive_builder = { version = "0.20" } +chrono = { version = "0.4" } +tokio = { version = "1.43", features = ["rt"]} diff --git a/packages/frontend/Elements.toml b/packages/frontend/Elements.toml new file mode 100644 index 0000000..6efce98 --- /dev/null +++ b/packages/frontend/Elements.toml @@ -0,0 +1 @@ +page_as_entry = true diff --git a/packages/frontend/assets/style/components/node.css b/packages/frontend/assets/style/components/node.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/style/components/search.css b/packages/frontend/assets/style/components/search.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/style/components/search_result.css b/packages/frontend/assets/style/components/search_result.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/style/components/viewport.css b/packages/frontend/assets/style/components/viewport.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/style/entry.css b/packages/frontend/assets/style/entry.css new file mode 100644 index 0000000..f6ad793 --- /dev/null +++ b/packages/frontend/assets/style/entry.css @@ -0,0 +1,187 @@ +/* %%% entry.css %%% */ + +/* %% root %% */ +:root { + /* % color palette % */ + --color-1: 17, 18, 19; + --color-2: 66, 69, 76; + --color-3: 95, 101, 115; + --color-4: 206, 214, 232; + --color-5: 250, 250, 250; + + /* % colors % */ + --background-color: var(--color-1); + --main-color: var(--color-4); + --text-color: var(--color-5); + --accent-color: var(--color-3); + --background-highlight-color: var(--color-2); + + /* % border % */ + --border-width: 0.15rem; + --border-color: var(--accent-color); + --border-radius: 1rem; +} + +/* %% general %% */ +/* % full size % */ +html, +body, +main, +#main, +svg, +section, +article, +aside { + height: 100%; + width: 100%; +} + +/* % no spacing % */ +html, +body, +main, +section, +article, +aside, +div { + margin: 0; + padding: 0; +} + +/* %% elements %% */ +/* % * % */ +* { + user-select: none; +} + +/* % html % */ +html { + background-color: rgb(var(--background-color)); + color: rgb(var(--text-color)); +} + +/* % Icon % */ +.Icon { + color: rgb(var(--accent-color)); + width: 2rem; + height: 2rem; + margin: 0.5rem; + padding: 0.5rem; +} + +/* % button % */ +button { + padding: 1rem; + margin: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +/* % NavbarButton % */ +.NavbarButton { + color: rgb(var(--accent-color)); + display: flex; + justify-content: space-between; + flex-direction: column; + align-items: center; +} + +.NavbarButton p { + display: flex; + justify-content: center; + align-items: center; + margin: 0; +} + +/* % a % */ +a { + text-decoration: none; + color: rgb(var(--text-color)); +} + +/* % form % */ +form { + align-items: center; + display: flex; + flex-direction: column; + height: 80%; + justify-content: space-around; + width: 90%; +} + +/* % header % */ +header { + width: 100%; + display: flex; + align-items: center; + justify-content: stretch; +} + +/* % input % */ +input { + border-width: var(--border-width); + background-color: rgba(var(--accent-color), 0.5); + border-color: rgb(var(--border-color)); + border-radius: var(--border-radius); + border-style: none; + caret-shape: block; + height: 3rem; + width: 100%; + color: rgb(var(--text-color)); + padding: 1rem; + margin: 1rem; +} + +input:focus { + outline: none; +} + +input::placeholder { + color: var(--input-grayed-out); + font-style: italic; +} + +/* input:focus::placeholder { */ +/* color: rgb(var(--accent-color)); */ +/* } */ + +input::-webkit-file-upload-button { + height: 100%; + width: 100%; + background-color: transparent; + border: none; + color: rgb(var(--border-color)); + font-weight: bold; +} + +input[type="range"] { + appearance: none; + -webkit-appearance: none; + height: 10px; + border-radius: 5px; + background: transparent; + outline: none; +} + +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--background-color); + cursor: pointer; +} + +input[type="range"]::-webkit-slider-runnable-track { + width: 100%; + height: 10px; + border-radius: 5px; + background: transparent; + display: flex; + align-items: center; +} + +input[type="range"]::-webkit-slider-thumb:focus { + background-color: rgb(var(--border-color)); +} diff --git a/packages/frontend/assets/style/components/connection.css b/packages/frontend/assets/style/items/connection.css similarity index 100% rename from packages/frontend/assets/style/components/connection.css rename to packages/frontend/assets/style/items/connection.css diff --git a/packages/frontend/assets/style/items/divider.css b/packages/frontend/assets/style/items/divider.css new file mode 100644 index 0000000..ecd8b6f --- /dev/null +++ b/packages/frontend/assets/style/items/divider.css @@ -0,0 +1,25 @@ +.Divider { + display: flex; + align-items: center; + justify-content: stretch; +} + +.Divider > .wrapper { + height: 100%; + width: 1rem; + margin: 0 calc(-0.5rem + var(--border-width) / 2); + display: flex; + align-items: center; + justify-content: center; +} + +.Divider > .wrapper:hover { + cursor: col-resize; +} + +.Divider > .wrapper > .inner { + background-color: rgba(var(--background-highlight-color), 0.5); + height: 100%; + width: var(--border-width); + z-index: 100; +} diff --git a/packages/frontend/assets/style/components/drag_area.css b/packages/frontend/assets/style/items/drag_area.css similarity index 100% rename from packages/frontend/assets/style/components/drag_area.css rename to packages/frontend/assets/style/items/drag_area.css diff --git a/packages/frontend/assets/style/components/draggable.css b/packages/frontend/assets/style/items/draggable.css similarity index 100% rename from packages/frontend/assets/style/components/draggable.css rename to packages/frontend/assets/style/items/draggable.css diff --git a/packages/frontend/assets/style/components/divider.css b/packages/frontend/assets/style/items/focus_button.css similarity index 100% rename from packages/frontend/assets/style/components/divider.css rename to packages/frontend/assets/style/items/focus_button.css diff --git a/packages/frontend/assets/style/components/labeled_box.css b/packages/frontend/assets/style/items/labeled_box.css similarity index 100% rename from packages/frontend/assets/style/components/labeled_box.css rename to packages/frontend/assets/style/items/labeled_box.css diff --git a/packages/frontend/assets/style/components/nav_button.css b/packages/frontend/assets/style/items/nav_button.css similarity index 100% rename from packages/frontend/assets/style/components/nav_button.css rename to packages/frontend/assets/style/items/nav_button.css diff --git a/packages/frontend/assets/style/items/node.css b/packages/frontend/assets/style/items/node.css new file mode 100644 index 0000000..8e34989 --- /dev/null +++ b/packages/frontend/assets/style/items/node.css @@ -0,0 +1,7 @@ +.Node { + border-width: var(--border-width); + background-color: rgba(var(--accent-color), 0.5); + border-color: rgb(var(--border-color)); + border-radius: var(--border-radius); + border-style: solid; +} diff --git a/packages/frontend/assets/style/components/runtime_param.css b/packages/frontend/assets/style/items/runtime_param.css similarity index 100% rename from packages/frontend/assets/style/components/runtime_param.css rename to packages/frontend/assets/style/items/runtime_param.css diff --git a/packages/frontend/assets/style/items/search.css b/packages/frontend/assets/style/items/search.css new file mode 100644 index 0000000..c7b2319 --- /dev/null +++ b/packages/frontend/assets/style/items/search.css @@ -0,0 +1,24 @@ +.Search { + display: flex; + /* overflow: hidden; */ + flex-direction: column; + justify-content: stretch; + align-items: center; + background-color: rgba(var(--background-highlight-color), 0.35); +} + +.Search main { + display: flex; + flex-direction: column; + justify-content: stretch; + align-items: center; + overflow-y: scroll; +} + +.Search header { + position: sticky; + background-color: rgba(var(--background-highlight-color), 0.5); +} + +.Search .results { +} diff --git a/packages/frontend/assets/style/items/search_result.css b/packages/frontend/assets/style/items/search_result.css new file mode 100644 index 0000000..c3728da --- /dev/null +++ b/packages/frontend/assets/style/items/search_result.css @@ -0,0 +1,17 @@ +.SearchResult { + flex-grow: 1; + border-width: var(--border-width); + background-color: rgba(var(--accent-color), 0.5); + border-color: rgb(var(--border-color)); + border-radius: var(--border-radius); + border-style: solid; + padding: 1.5rem; + margin: 1rem; +} + +.Draggable:has(.SearchResult) { + display: flex; + align-items: center; + justify-content: center; + width: 100%; +} diff --git a/packages/frontend/assets/style/components/static_param.css b/packages/frontend/assets/style/items/static_param.css similarity index 100% rename from packages/frontend/assets/style/components/static_param.css rename to packages/frontend/assets/style/items/static_param.css diff --git a/packages/frontend/assets/style/items/viewport.css b/packages/frontend/assets/style/items/viewport.css new file mode 100644 index 0000000..1a39094 --- /dev/null +++ b/packages/frontend/assets/style/items/viewport.css @@ -0,0 +1,3 @@ +.Viewport { + background-color: rgba(var(--background-highlight-color), 0.1); +} diff --git a/packages/frontend/assets/style/main.css b/packages/frontend/assets/style/main.css deleted file mode 100644 index 4c54a96..0000000 --- a/packages/frontend/assets/style/main.css +++ /dev/null @@ -1,14 +0,0 @@ -svg { - height: 100%; - width: 100%; -} - -.Icon { - width: 2rem; - height: 2rem; -} - -.button { - padding: 1rem; - margin: 1rem; -} diff --git a/packages/frontend/assets/style/pages/editor.css b/packages/frontend/assets/style/pages/editor.css index e69de29..32c8707 100644 --- a/packages/frontend/assets/style/pages/editor.css +++ b/packages/frontend/assets/style/pages/editor.css @@ -0,0 +1,3 @@ +aside { + width: 50rem; +} diff --git a/packages/frontend/assets/style/pages/search.css b/packages/frontend/assets/style/pages/search.css index e69de29..546c4b4 100644 --- a/packages/frontend/assets/style/pages/search.css +++ b/packages/frontend/assets/style/pages/search.css @@ -0,0 +1,2 @@ +.Search { +} diff --git a/packages/frontend/assets/style/pages/start.css b/packages/frontend/assets/style/pages/start.css index e69de29..16da738 100644 --- a/packages/frontend/assets/style/pages/start.css +++ b/packages/frontend/assets/style/pages/start.css @@ -0,0 +1,19 @@ +/* %%% pages / start.css %%% */ +main { + display: flex; + align-items: center; + justify-content: center; +} + +div .button-container { + width: 30rem; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.NavbarButton .Icon { + height: 3rem; + width: 3rem; +} diff --git a/packages/frontend/src/modules.rs b/packages/frontend/src/modules.rs index 0514776..e1410e7 100644 --- a/packages/frontend/src/modules.rs +++ b/packages/frontend/src/modules.rs @@ -4,3 +4,4 @@ pub mod components; pub mod icons; pub mod pages; +pub mod utils; diff --git a/packages/frontend/src/modules/components.rs b/packages/frontend/src/modules/components.rs index 199bfd8..241bf4e 100644 --- a/packages/frontend/src/modules/components.rs +++ b/packages/frontend/src/modules/components.rs @@ -5,12 +5,15 @@ pub mod connection; pub mod divider; pub mod drag_area; pub mod draggable; +pub mod focus_button; +pub mod focus_button_array; pub mod labeled_box; pub mod nav_button; pub mod node; pub mod runtime_param; pub mod search; pub mod search_result; +pub mod section_toggle; pub mod static_param; pub mod viewport; @@ -19,11 +22,14 @@ pub mod prelude { pub use super::divider::*; pub use super::drag_area::*; pub use super::draggable::*; + pub use super::focus_button::*; + pub use super::focus_button_array::*; 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::section_toggle::*; pub use super::viewport::*; pub mod params { pub use super::super::connection::*; @@ -34,6 +40,7 @@ pub mod prelude { // %% utils %% pub(crate) mod utils { + pub use crate::modules::utils::*; pub use crate::utils::*; - pub use simple_ai_backend::utils::prelude::*; + pub use simple_ai_backend::prelude::*; } diff --git a/packages/frontend/src/modules/components/connection.rs b/packages/frontend/src/modules/components/connection.rs index 651b1f0..43e91d8 100644 --- a/packages/frontend/src/modules/components/connection.rs +++ b/packages/frontend/src/modules/components/connection.rs @@ -121,7 +121,7 @@ pub fn Connection(intern: InternConnection) -> Element { path { class: "Curve", stroke_width: "{stroke_width()}px", - d: "{svg_path()}" + d: "{svg_path()}", } } } diff --git a/packages/frontend/src/modules/components/divider.rs b/packages/frontend/src/modules/components/divider.rs index c92eea2..8d3b943 100644 --- a/packages/frontend/src/modules/components/divider.rs +++ b/packages/frontend/src/modules/components/divider.rs @@ -6,24 +6,22 @@ use super::utils::*; // %% main %% #[component] pub fn Divider(children: Element) -> Element { - let script = r#####" -((c) =>{ - console.log("hello"); - let l = c.parentElement; - console.log(l); - let middleIndex = Math.floor(l.children.length / 2); - let wrapper = document.createElement("div"); - wrapper.className = "wrapper"; - let div = document.createElement("div"); - div.className = "inner"; - wrapper.appendChild(div); - l.insertBefore(wrapper, l.children[middleIndex + 1]); -})(document.currentScript); -"#####; + let script = r#" + let l = document.currentScript.parentElement; + let middleIndex = Math.floor(l.children.length / 2); + let wrapper = document.createElement("div"); + wrapper.className = "wrapper"; + let div = document.createElement("div"); + div.className = "inner"; + wrapper.appendChild(div); + l.insertBefore(wrapper, l.children[middleIndex + 1]); + "#; rsx! { - div { + article { class: "Divider", - script { { script } } + script { + { script } + } { children } } } diff --git a/packages/frontend/src/modules/components/drag_area.rs b/packages/frontend/src/modules/components/drag_area.rs index 2774464..87ba18a 100644 --- a/packages/frontend/src/modules/components/drag_area.rs +++ b/packages/frontend/src/modules/components/drag_area.rs @@ -6,9 +6,10 @@ use super::utils::*; // %% main %% #[derive(Default, Clone, Copy)] pub struct DragContext { - pub cursor_start_position: Signal, - pub dragging: Signal, - pub distance: Signal, + pub cursor_handle: Signal, + cursor_start_position: Signal, + dragging: Signal, + distance: Signal, } impl DragContext { pub fn new() -> Self { @@ -31,10 +32,13 @@ impl DragContext { self.distance.take(); } pub fn dragging(&self) -> bool { - self.dragging.cloned() + (self.dragging)() } pub fn distance(&self) -> PageVector { - self.distance.cloned() + (self.distance)() + } + pub fn reset_cursor(&mut self) { + self.cursor_handle.set("unset".into()); } } @@ -43,12 +47,11 @@ 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); - }; + let mouseup = move |e: MouseEvent| context.end(e); rsx! { - div { + article { class: "DragArea", + cursor: "{(context.cursor_handle)()}", onmousedown: mousedown, onmousemove: mousemove, onmouseup: mouseup, diff --git a/packages/frontend/src/modules/components/draggable.rs b/packages/frontend/src/modules/components/draggable.rs index 75d5485..1bafe85 100644 --- a/packages/frontend/src/modules/components/draggable.rs +++ b/packages/frontend/src/modules/components/draggable.rs @@ -3,6 +3,8 @@ // %% includes %% use super::drag_area::DragContext; use super::utils::*; +use dioxus::html::geometry::euclid::default::SideOffsets2D; +use dioxus::html::geometry::euclid::Rect; // %% main %% #[component] @@ -10,74 +12,115 @@ pub fn Draggable( #[props(default)] ondraggingstart: Callback, #[props(default)] ondragging: Callback, #[props(default)] ondraggingend: Callback, + #[props(default)] height_handle: Signal, + #[props(default)] width_handle: Signal, + #[props(default)] dims_handle: Signal, #[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)] style_position_save: Signal, + #[props(default = Signal::new("unset".into()))] style_position_handle: Signal, + #[props(default = Signal::new(10))] z_index_handle: Signal, + #[props(default)] display_handle: Signal, + #[props(default)] user_select_handle: Signal, + #[props(default)] cursor_handle: Signal, #[props(default)] pressed: Signal, - #[props(default)] mounted_data: Signal>>, + #[props(default = true)] pop_back: bool, 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() + // using a use_signal as a variable here because first its private and second it just does not work (this was a pain in the ass to find out...) + let mut mounted_data: Signal>> = use_signal(|| None); + + let mut reset_dims = move || { + height_handle.set("unset".into()); + width_handle.set("unset".into()); }; - let context: DragContext = use_context(); + let mut set_dims = move || { + height_handle.set(format!("{}px", dims_handle().y)); + width_handle.set(format!("{}px", dims_handle().x)); + }; + + let mut 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"); + let rect = mounted_data().unwrap().get_client_rect().await.unwrap(); + + // set a absolute size and position instead of 100% for example + position_handle.set(rect.origin.to_vector().cast_unit()); + dims_handle.set(rect.size.to_vector().cast_unit()); + set_dims(); + position_save.set(position_handle()); - user_select_handle.set("none"); + style_position_save.set(style_position_handle()); // save the original position property of css to reset it later if using pop_back + + // using position absolute here because it has to be removed form document flow + style_position_handle.set("absolute".into()); + + // cosmetic + context.cursor_handle.set("grabbing".into()); + user_select_handle.set("none".into()); + pressed.set(true); + + // callback with the position save 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()); + let _ = use_resource(move || async move { + // let the distance signal of the context be a dependency that drives this hook + let distance = context.distance(); + + // Check if exactly this one element is pressed + if !*pressed.peek() { + return; + }; + + // The distance is from the original position so we also have to use this if we want to use + // display absolute instead of relative + let position = position_save() + distance; + + // We check if the context is dragging to tell if the user stopped dragging when the cursor + // is out of bounds for this type too, because it doesn't work to just use the onmouseup on this type. + if context.dragging() { + position_handle.set(position); + ondragging.call(position); // When really dragging provide an event (callback) + } else { + let rect = mounted_data().unwrap().get_client_rect().await.unwrap(); + ondraggingend.call( + *position_handle.peek() + PageVector::new(rect.width(), rect.height()) / 2f64, + ); // When not dragging provide an event + if pop_back { + position_handle.set(Vector2D::zero()); // This is set to zero because it actually + // refers to the translate property in css style_position_handle.set(style_position_save()); - user_select_handle.set("unset"); - *pressed.write_silent() = false; - cursor_handle.set("unset"); - } + reset_dims(); + }; + context.cursor_handle.set("grab".into()); + pressed.set(false); } }); - let mounted = move |e: MountedEvent| async move { + let mounted = move |e: MountedEvent| { mounted_data.set(Some(e.data())); }; - let mouseover = move |_| { - cursor_handle.set("grab"); + let mouseenter = move |_| { + if !context.dragging() { + context.cursor_handle.set("grab".into()); + } + }; + + let mouseleave = move |_| { + if !context.dragging() { + context.reset_cursor(); + } }; rsx! { div { class: "Draggable", - width: "fit-content", - height: "fit-content", top: 0, left: 0, + height: height_handle, + width: width_handle, z_index: z_index_handle, display: "{display_handle}", position: "{style_position_handle}", @@ -86,7 +129,8 @@ pub fn Draggable( transform: "translate({position_handle.read().x}px, {position_handle.read().y}px)", onmousedown: mousedown, onmounted: mounted, - onmouseover: mouseover, + onmouseenter: mouseenter, + onmouseleave: mouseleave, { children } } } diff --git a/packages/frontend/src/modules/components/focus_button.rs b/packages/frontend/src/modules/components/focus_button.rs new file mode 100644 index 0000000..1438143 --- /dev/null +++ b/packages/frontend/src/modules/components/focus_button.rs @@ -0,0 +1,33 @@ +// %%% components / nav_button.rs %%% + +// %% includes %% +use super::utils::*; + +// %% main %% + +#[component] +pub fn FocusButton( + #[props(default)] children: Element, + #[props(default)] onfocus: Callback<()>, + #[props(default)] onunfocus: Callback<()>, + #[props(default)] onclick: Callback, + #[props(default = use_signal(|| false))] focused: Signal, + #[props(extends = GlobalAttributes)] attributes: Vec, +) -> Element { + rsx! { + button { + "focused": focused(), + onclick: move |e| { + if focused() { + onunfocus.call(()); + } else { + onfocus.call(()); + } + focused.toggle(); + onclick.call(e); + }, + ..attributes, + {children} + } + } +} diff --git a/packages/frontend/src/modules/components/focus_button_array.rs b/packages/frontend/src/modules/components/focus_button_array.rs new file mode 100644 index 0000000..da9949d --- /dev/null +++ b/packages/frontend/src/modules/components/focus_button_array.rs @@ -0,0 +1,56 @@ +// %%% components / nav_button.rs %%% + +// %% includes %% +use super::focus_button::*; +use super::search::Search; +use super::utils::*; +use std::collections::HashMap; + +// %% main %% + +// #[item(no_css = true)] +#[component] +pub fn FocusButtonArrayItem( + children: Element, + #[props(extends = GlobalAttributes)] attributes: Vec, +) -> Element { + rsx! { + FocusButton { + div { ..attributes,{children} } + } + } +} + +// #[item(no_css = true)] +#[component] +pub fn FocusButtonArray( + children: Element, + #[props(default)] onfocus: Callback<()>, + #[props(default)] onunfocus: Callback<()>, + #[props(default)] onclick: Callback, + #[props(default = use_signal(|| false))] focused: Signal, + #[props(extends = GlobalAttributes)] attributes: Vec, +) -> Element { + let mut section_contents_map = use_signal(HashMap::new); + rsx! { + nav { + FocusButton { + onfocus: move || { + section_contents_map.write().insert("search", rsx! { + Search {} + "Hello" + }); + }, + onunfocus: move || { + section_contents_map.write().remove("search"); + }, + SearchIcon {} + } + } + section { + for (_ , e) in section_contents_map() { + {e} + } + } + } +} diff --git a/packages/frontend/src/modules/components/labeled_box.rs b/packages/frontend/src/modules/components/labeled_box.rs index 79bed93..0fad754 100644 --- a/packages/frontend/src/modules/components/labeled_box.rs +++ b/packages/frontend/src/modules/components/labeled_box.rs @@ -18,10 +18,9 @@ pub fn LabeledBox(children: Element) -> Element { })(document.currentScript); "#####; rsx! { - div { - class: "LabeledBox", - script { { script } } - { children } + div { class: "LabeledBox", + script { {script} } + {children} } } } diff --git a/packages/frontend/src/modules/components/nav_button.rs b/packages/frontend/src/modules/components/nav_button.rs index e028952..1ffe431 100644 --- a/packages/frontend/src/modules/components/nav_button.rs +++ b/packages/frontend/src/modules/components/nav_button.rs @@ -2,24 +2,19 @@ // %% includes %% use super::utils::*; -use dioxus::router::prelude::*; +use dioxus::router::NavigationTarget; // %% main %% -#[component] +#[item] pub fn NavButton( children: Element, - class: Option, #[props(into)] to: NavigationTarget, + #[props(extends = GlobalAttributes)] attributes: Vec, ) -> Element { - let class_unw = class.unwrap_or_default(); rsx! { - Link { - class: "NavbarButton button {class_unw}", - to: to, - div { - {children} - } + Link { to, class: "asdf", + div { ..attributes,{children} } } } } diff --git a/packages/frontend/src/modules/components/node.rs b/packages/frontend/src/modules/components/node.rs index beae957..18e4298 100644 --- a/packages/frontend/src/modules/components/node.rs +++ b/packages/frontend/src/modules/components/node.rs @@ -6,6 +6,11 @@ use super::static_param::{InternStaticParam, StaticParam}; use super::utils::*; // %% main %% +// % transferrer % // +pub static NODE_TRANSFERER: GlobalSignal> = + GlobalSignal::new(|| None); + +// % Node % // #[derive(PartialEq, Props, Clone)] pub struct InternNode { pub node: StrongNode, diff --git a/packages/frontend/src/modules/components/search.rs b/packages/frontend/src/modules/components/search.rs index bcc0539..11925aa 100644 --- a/packages/frontend/src/modules/components/search.rs +++ b/packages/frontend/src/modules/components/search.rs @@ -3,31 +3,54 @@ // %% includes %% use super::search_result::{InternSearchResult, SearchResult}; use super::utils::*; +use chrono::Utc; +use simple_ai_backend::modules::utils::node::NodeBuilder; // %% main %% #[component] -pub fn Search() -> Element { +pub fn Search(#[props(extends = GlobalAttributes)] attributes: Vec) -> Element { let mut intern_search_results = use_signal(Vec::::new); - let mut search_results = use_signal(Vec::::new); + let mut search_results = use_signal(Container::new); let input = move |e: FormEvent| { - search_results.set(search(e.value())); + search_results.set(query::query_nodes(vec![QueryFilter::Name { + name: e.value(), + }])); intern_search_results.clear(); }; use_effect(move || { intern_search_results.set( search_results() - .iter_mut() - .map(|result| InternSearchResult::from(result.clone())) + .iter() + .map(|result| InternSearchResult::from(result.context.blocking_lock().clone())) .collect::>(), ); }); + // Todo: remove this its just a test + let intern = InternSearchResult::from( + NodeBuilder::default() + .name("SampleNode".to_string()) + .params(Vec::new()) + .version(Version { + version: "0.0.1".to_string(), + env: Environment { deps: Vec::new() }, + }) + .kind(NodeKind::Bundled { + bundle: Container::new(), + }) + .description("this is a sample node".to_string()) + .author("sert".to_string()) + .date(Utc::now()) + .build() + .unwrap(), + ); + rsx! { - div { - overflow: "visible", + article { class: "Search", + ..attributes, header { input { oninput: input, @@ -41,14 +64,18 @@ pub fn Search() -> Element { // } } main { - overflow: "visible", div { class: "spacer" } section { class: "results", - height: "100%", for intern in intern_search_results() { SearchResult { intern } } + SearchResult { intern } + SearchResult { intern } + SearchResult { intern } + SearchResult { intern } + SearchResult { intern } + SearchResult { intern } } } } diff --git a/packages/frontend/src/modules/components/search_result.rs b/packages/frontend/src/modules/components/search_result.rs index 244a865..d74a356 100644 --- a/packages/frontend/src/modules/components/search_result.rs +++ b/packages/frontend/src/modules/components/search_result.rs @@ -2,9 +2,12 @@ // %% includes %% use super::draggable::Draggable; +use super::node::NODE_TRANSFERER; use super::utils::*; // %% main %% + +// % Search Result % // #[derive(PartialEq, Props, Clone, Copy)] pub struct InternSearchResult { pub node: Signal, @@ -21,7 +24,7 @@ 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); + *NODE_TRANSFERER.write() = Some(node); }; rsx! { @@ -32,12 +35,10 @@ pub fn SearchResult(intern: InternSearchResult) -> Element { div { class: "wrapper items", h3 { span { id: "name", "{intern.node.cloned().name}" } } - div { - class: "wrapper", + class: "wrapper", div { class: "wrapper i", - div { id: "open", class: "icon" } } } diff --git a/packages/frontend/src/modules/components/section_toggle.rs b/packages/frontend/src/modules/components/section_toggle.rs new file mode 100644 index 0000000..7bb1a28 --- /dev/null +++ b/packages/frontend/src/modules/components/section_toggle.rs @@ -0,0 +1,44 @@ +// %%% components / nav_button.rs %%% + +// %% includes %% +// use dioxus::{dioxus_core::AttributeValue, router::NavigationTarget}; +use super::focus_button::*; +use super::search::Search; +use super::utils::*; +use std::collections::HashMap; + +// %% main %% + +#[component] +// #[component(no_css = true)] +pub fn FocusButtonArray( + children: Element, + #[props(default)] onfocus: Callback<()>, + #[props(default)] onunfocus: Callback<()>, + #[props(default)] onclick: Callback, + #[props(default = use_signal(|| false))] focused: Signal, + #[props(extends = GlobalAttributes)] attributes: Vec, +) -> Element { + let mut section_contents_map = use_signal(HashMap::new); + rsx! { + nav { + FocusButton { + onfocus: move || { + section_contents_map.write().insert("search", rsx! { + Search {} + "Hello" + }); + }, + onunfocus: move || { + section_contents_map.write().remove("search"); + }, + SearchIcon {} + } + } + section { + for (_ , e) in section_contents_map() { + {e} + } + } + } +} diff --git a/packages/frontend/src/modules/components/static_param.rs b/packages/frontend/src/modules/components/static_param.rs index 58cb5a5..e508d85 100644 --- a/packages/frontend/src/modules/components/static_param.rs +++ b/packages/frontend/src/modules/components/static_param.rs @@ -2,7 +2,7 @@ // %% includes %% use super::utils::*; -use simple_ai_backend::utils::prelude::*; +use simple_ai_backend::prelude::*; // %% main %% #[derive(PartialEq, Props, Clone)] diff --git a/packages/frontend/src/modules/components/viewport.rs b/packages/frontend/src/modules/components/viewport.rs index 264baeb..99d9af8 100644 --- a/packages/frontend/src/modules/components/viewport.rs +++ b/packages/frontend/src/modules/components/viewport.rs @@ -1,8 +1,10 @@ // %%% components / viewport.rs %%% +use crate::modules::pages::utils::NODE_TRANSFERER; + // %% includes %% use super::utils::*; -use simple_ai_backend::utils::prelude::*; +use simple_ai_backend::prelude::*; // %% main %% #[derive(Clone)] @@ -75,16 +77,16 @@ pub fn Viewport( } }; - 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)); - // } + let _ = use_resource(move || async move { + let mut ctx = NODE_TRANSFERER(); + 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 ------------------------------ // @@ -170,7 +172,7 @@ pub fn Viewport( }; rsx! { - body { + article { class: "Viewport", cursor: "{cursor}", overflow: "hidden", diff --git a/packages/frontend/src/modules/icons.rs b/packages/frontend/src/modules/icons.rs index 28e5e92..175dbaa 100644 --- a/packages/frontend/src/modules/icons.rs +++ b/packages/frontend/src/modules/icons.rs @@ -6,6 +6,7 @@ use crate::utils::*; // %% main %% icon! { search: , - new: , - editor: + new: , + editor: , + market: } diff --git a/packages/frontend/src/modules/pages/editor.rs b/packages/frontend/src/modules/pages/editor.rs index 3eb03fc..65b059b 100644 --- a/packages/frontend/src/modules/pages/editor.rs +++ b/packages/frontend/src/modules/pages/editor.rs @@ -2,24 +2,37 @@ // %% includes %% use super::utils::*; +use std::collections::HashMap; // %% main %% -#[page] +#[component] pub fn Editor() -> Element { + let mut section_contents_map = use_signal(HashMap::new); + rsx! { - style { "html {{overflow: hidden;}} * {{ user_select: none }}" } main { DragArea { - Divider - { - section { - Viewport {} - } - aside { - z_index: 2, - nav {} + Divider { + section { Viewport {} } + aside { z_index: 2, + nav { + FocusButton { + onfocus: move || { + section_contents_map.write().insert("search", rsx! { + Search {} + "Hello" + }); + }, + onunfocus: move || { + section_contents_map.write().remove("search"); + }, + SearchIcon {} + } + } section { - Search {} + for (_ , e) in section_contents_map() { + {e} + } } } } diff --git a/packages/frontend/src/modules/pages/new.rs b/packages/frontend/src/modules/pages/new.rs index 9f140bb..ea9f1f8 100644 --- a/packages/frontend/src/modules/pages/new.rs +++ b/packages/frontend/src/modules/pages/new.rs @@ -4,41 +4,64 @@ use super::utils::*; // %% main %% -#[page] +#[component] pub fn New() -> Element { rsx! { main { form { LabeledBox { - label { for: "name", "node name" } - input { id: "name", name: "name", type: "text", required: "true", placeholder: "SampleProject"} + label { r#for: "name", "node name" } + input { + id: "name", + name: "name", + r#type: "text", + required: "true", + placeholder: "SampleProject", + } } LabeledBox { - input { id: "name", name: "name", type: "text", required: "true", placeholder: "SampleProject"} - label { for: "name", "project name" } + input { + id: "name", + name: "name", + r#type: "text", + required: "true", + placeholder: "SampleProject", + } + label { r#for: "name", "project name" } } - input { type: "text" } + input { r#type: "text" } LabeledBox { - label { for: "name", "project name" } - input { id: "name", name: "name", type: "text", required: "true", placeholder: "SampleProject"} + label { r#for: "name", "project name" } + input { + id: "name", + name: "name", + r#type: "text", + required: "true", + placeholder: "SampleProject", + } } LabeledBox { - input { id: "name", name: "name", type: "text", required: "true", placeholder: "SampleProject"} - label { for: "name", "project name" } + input { + id: "name", + name: "name", + r#type: "text", + required: "true", + placeholder: "SampleProject", + } + label { r#for: "name", "project name" } } - input { type: "file" } - input { type: "range" } + input { r#type: "file" } + input { r#type: "range" } - input { type: "list", list: "options" } + input { r#type: "list", list: "options" } - datalist { - id: "options", + datalist { id: "options", option { value: "1", "1" } option { value: "2", "2" } option { value: "3", "3" } } - button { type: "submit", "create" } + button { r#type: "submit", "create" } } } } diff --git a/packages/frontend/src/modules/pages/search.rs b/packages/frontend/src/modules/pages/search.rs index f5cbefe..a60d5f5 100644 --- a/packages/frontend/src/modules/pages/search.rs +++ b/packages/frontend/src/modules/pages/search.rs @@ -7,8 +7,6 @@ use super::utils::*; #[page] pub fn Search() -> Element { rsx! { - main { - Search {} - } + main { Search {} } } } diff --git a/packages/frontend/src/modules/pages/start.rs b/packages/frontend/src/modules/pages/start.rs index cf77ddd..15894e0 100644 --- a/packages/frontend/src/modules/pages/start.rs +++ b/packages/frontend/src/modules/pages/start.rs @@ -2,7 +2,7 @@ // %% includes %% use super::utils::*; -use dioxus::router::prelude::*; +use dioxus::router::NavigationTarget; // %% main %% #[page] @@ -13,11 +13,18 @@ pub fn Start( ) -> Element { rsx! { main { - article { - section { - NavButton { class: "search", to: search_route, SearchIcon {}} - NavButton { class: "new", to: new_route, NewIcon {} } - NavButton { class: "editor", to: editor_route, EditorIcon {} } + div { class: "button-container", + NavButton { class: "search", to: search_route, + SearchIcon {} + p { "search" } + } + NavButton { class: "new", to: new_route, + NewIcon {} + p { "new" } + } + NavButton { class: "editor", to: editor_route, + EditorIcon {} + p { "editor" } } } } diff --git a/packages/frontend/src/modules/utils.rs b/packages/frontend/src/modules/utils.rs new file mode 100644 index 0000000..f560554 --- /dev/null +++ b/packages/frontend/src/modules/utils.rs @@ -0,0 +1,8 @@ +use dioxus::dioxus_core::{Attribute, AttributeValue}; +pub fn add_to_classes(class: &str, attributes: &mut Vec) { + for a in attributes { + if a.name == "class" { + a.value = AttributeValue::Text(format!("{} {:?}", class, a.value)); + } + } +} diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml index 3b958bc..e0667f9 100644 --- a/packages/macros/Cargo.toml +++ b/packages/macros/Cargo.toml @@ -14,3 +14,10 @@ change-case = { version = "0.2.0" } dioxus-rsx-rosetta = { version = "0.6" } dioxus-autofmt = { version = "0.6" } rstml = { version = "0.12" } +toml = { version = "0.9" } +serde = { version = "1.0" } +dioxus-rsx = { version = "0.7.0-rc.0"} + +[dev-dependencies] +dioxus = { workspace = true } +macrotest = { version = "1.2" } diff --git a/packages/macros/src/component.rs b/packages/macros/src/component.rs deleted file mode 100644 index 206b2f5..0000000 --- a/packages/macros/src/component.rs +++ /dev/null @@ -1,6 +0,0 @@ -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 index 6dfa522..de38590 100644 --- a/packages/macros/src/element.rs +++ b/packages/macros/src/element.rs @@ -1,73 +1,19 @@ -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 style_asset_path = std::path::PathBuf::from("/assets").join("style"); - - let pathbuf_asset_path = style_asset_path - .join(str_type_name) - .join(str_lowercase_emt_name + ".css"); - - let pathbuf_main_css_path = style_asset_path.join("main.css"); - - let asset_path = LitStr::new( - pathbuf_asset_path.to_str().unwrap_or_default(), - Span::call_site(), - ); - - let main_css_path = LitStr::new( - pathbuf_main_css_path.to_str().unwrap_or_default(), - Span::call_site(), - ); - - let mut main_css_link = quote! {}; - let mut main_css_asset = quote! {}; - if str_type_name == "pages" { - main_css_link = quote! { - document::Link { rel: "stylesheet", href: MAIN_CSS }, - }; - main_css_asset = quote! { - use dioxus::prelude::{asset, manganis, Asset}; - const MAIN_CSS: Asset = asset!(#main_css_path); - }; - } - for stmt in &mut out_items.block.stmts { - if let Stmt::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 new_tokens = TokenStream::new(); - quote! { - document::Link { rel: "stylesheet", href: #css_emt_name }, - #main_css_link - } - .to_tokens(&mut new_tokens); - rsx_tokens.to_tokens(&mut new_tokens); - new_tokens - }; - } - } - } - - let out = quote! { - #main_css_asset - const #css_emt_name: Asset = asset!(#asset_path); - #[dioxus::core_macro::component] - #out_items - }; - - println!("out quote: {}", out.to_string()); - - out +// %%% element.rs %%% + +// %% modules %% +pub mod attrs; +pub mod function; +pub mod handler; +pub mod kind; +pub mod manifest; +pub mod rsx_ast; + +// %% includes %% +use handler::ElementHandler; +use proc_macro2::TokenStream; + +// %% main %% +// % impl % +pub fn macro_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + ElementHandler::new(attr, item).handle() } diff --git a/packages/macros/src/element/attrs.rs b/packages/macros/src/element/attrs.rs new file mode 100644 index 0000000..e47bf41 --- /dev/null +++ b/packages/macros/src/element/attrs.rs @@ -0,0 +1,89 @@ +// %%% args.rs %%% +// %% includes %% +// % intern % +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Expr, ExprLit, Lit, LitBool, LitStr, MetaNameValue, Token, +}; +// % extern % +use super::kind::ElementKind; +// %% main %% +// % MetaArgs % +struct MetaArgs { + pub args: Punctuated, +} + +impl Parse for MetaArgs { + fn parse(input: ParseStream) -> syn::Result { + Ok(MetaArgs { + args: input.parse_terminated(MetaNameValue::parse, Token![,])?, + }) + } +} + +// % ElementArgs % +pub struct ElementAttrs { + pub kind: ElementKind, + pub no_css: bool, + pub no_class: bool, + pub entry: bool, +} + +impl Default for ElementAttrs { + fn default() -> Self { + Self { + kind: ElementKind::Item, + no_css: false, + no_class: false, + entry: false, + } + } +} + +impl Parse for ElementAttrs { + fn parse(input: ParseStream) -> syn::Result { + Ok(MetaArgs::parse(input)?.into()) + } +} + +impl From for ElementAttrs { + fn from(MetaArgs { args }: MetaArgs) -> Self { + let mut r = Self::default(); + for arg in args { + match arg.path.get_ident().unwrap().to_string().as_str() { + "kind" => { + r.kind = match arg.value { + Expr::Lit(ExprLit{ lit, .. }) => lit.into(), + _ => panic!("Please use a literal like 'Component', 'Page' or 'Entry' for the value of 'kind': here --> {:?}", arg.path), + } + } + "no_css" => { + r.no_css = parse_bool("no_css", &arg).unwrap(); + } + "no_class" => { + r.no_class = parse_bool("no_class", &arg).unwrap(); + } + "entry" => { + r.entry = parse_bool("entry", &arg).unwrap(); + } + _ => {} + } + } + r + } +} + +fn parse_bool<'a>(when: &'a str, arg: &'a MetaNameValue) -> Result { + if let Expr::Lit(ExprLit { + lit: Lit::Bool(LitBool { value, .. }), + .. + }) = &arg.value + { + return Ok(*value); + } + Err(format!( + "Please use a valid boolean for '{}': here --> {:?}", + when, arg.path + )) +} diff --git a/packages/macros/src/element/function.rs b/packages/macros/src/element/function.rs new file mode 100644 index 0000000..61657ad --- /dev/null +++ b/packages/macros/src/element/function.rs @@ -0,0 +1,66 @@ +// %%% function.rs %%% +// %% includes %% +// % extern % +use change_case::*; +use syn::{ + parse::{Parse, ParseStream}, + ItemFn, Macro, Stmt, +}; +// % intern % +// %% main %% +// % Element Function % +pub struct ElementFunction { + pub function: ItemFn, + pub name: String, + pub element_name: String, +} + +impl ElementFunction { + fn extract_macro_stmt_mut(&mut self) -> &mut Stmt { + let stmt = self + .function + .block + .stmts + .last_mut() + .expect("There are no statements in your function"); + + if let Stmt::Macro(_) = stmt { + stmt + } else { + panic!("Couldn't extract the rsx! block from the function") + } + } + + pub fn extract_rsx_macro_stmt_mut(&mut self) -> &mut Stmt { + let stmt = self.extract_macro_stmt_mut(); + if let Stmt::Macro(mac) = stmt { + if mac.mac.path.segments.last().unwrap().ident != "rsx" { + panic!("The last statement as a macro is no rsx macro"); + } + } else { + panic!(); + } + stmt + } + + pub fn extract_rsx_macro_mut(&mut self) -> &mut Macro { + if let Stmt::Macro(mac) = self.extract_rsx_macro_stmt_mut() { + &mut mac.mac + } else { + panic!() + } + } +} + +impl Parse for ElementFunction { + fn parse(input: ParseStream) -> syn::Result { + let function: ItemFn = ItemFn::parse(input)?; + let name = function.sig.ident.to_string(); + let element_name = pascal_case(&name); + Ok(Self { + function, + name, + element_name, + }) + } +} diff --git a/packages/macros/src/element/handler.rs b/packages/macros/src/element/handler.rs new file mode 100644 index 0000000..b8f7e6b --- /dev/null +++ b/packages/macros/src/element/handler.rs @@ -0,0 +1,114 @@ +// %%% ipl.rs %%% +// this is the implementation of the element macro, which is for handling the style assets and the +// creation of new dioxus components + +// %% includes %% +// % extern % +use crate::element::rsx_ast::{Attribute, Attributes, Element}; +use dioxus_rsx::{ + AttributeName, AttributeValue, BodyNode, CallBody, Component, DynIdx, HotLiteral, + HotReloadFormattedSegment, IfmtInput, RsxBlock, RsxItem, Segment, TemplateBody, +}; +use proc_macro2::{Span, TokenStream}; +use quote::*; +use syn::{parse::Parser, parse2, parse_quote, Expr, Ident, LitStr, Macro, Stmt}; + +// % intern % +use super::{ + attrs::ElementAttrs, function::ElementFunction, kind::ElementKind, manifest::ElementConfig, +}; + +// %% main %% +pub struct ElementHandler { + pub attrs: ElementAttrs, + pub function: ElementFunction, + pub config: ElementConfig, +} + +impl ElementHandler { + pub fn new(attr: TokenStream, item: TokenStream) -> Self { + Self { + attrs: parse2(attr).expect("Could not parse the function attributes"), + function: parse2(item).expect("Could not parse the function itself"), + config: ElementConfig::new(), + } + } + + pub fn handle_as_entries(&mut self) { + match self.attrs.kind { + ElementKind::Page => { + if self.config.page_as_entry { + self.attrs.entry = true; + } + } + ElementKind::Item => { + if self.config.component_as_entry { + self.attrs.entry = true; + } + } + _ => {} + } + + if self.attrs.entry { + self.attrs.kind = ElementKind::Entry; + self.attrs.no_css = false; + self.handle_css(rsx_main_element); + } + } + + pub fn handle(&mut self) -> TokenStream { + self.handle_css(); + self.handle_as_entries(); + self.handle_class(); + + let func = &self.function.into_token_stream(); + quote! { + #[dioxus::prelude::component] + #func + } + } + + fn handle_css(&mut self) { + if self.attrs.no_css { + return; + } + + let style_file = self + .config + .asset_dir + .join(self.attrs.kind.style_file(&self.function.name)); + + let style_file_lit = + LitStr::new(style_file.to_str().unwrap_or_default(), Span::call_site()); + + rsx_main_element.elmts.push(parse_quote! { + document::Link { + rel: "stylesheet", + href: asset!(#style_file_lit) + }}); + } + + fn handle_class(&mut self) { + if self.attrs.no_class { + return; + } + let elm = { + if let Some(elm) = rsx_main_element + .attrs + .iter_mut() + .find(|attr| attr.name == "class") + { + elm + } else { + rsx_main_element.attrs.push(parse_quote! { class: "", }); + rsx_main_element.attrs.last_mut().unwrap() + } + }; + let val = parse2::(elm.value.clone()).expect("Couldn't parse attribute value"); + elm.value = LitStr::new( + &format!("{} Element {}", val.value(), self.function.name), + Span::call_site(), + ) + .into_token_stream(); + } +} diff --git a/packages/macros/src/element/kind.rs b/packages/macros/src/element/kind.rs new file mode 100644 index 0000000..5638ffc --- /dev/null +++ b/packages/macros/src/element/kind.rs @@ -0,0 +1,56 @@ +// %%% kind.rs %%% +// %% includes %% +// % intern % +use crate::element::ElementHandler; +// % extern % +use change_case::snake_case; +use proc_macro2::TokenStream; +use std::path::PathBuf; +use syn::Lit; + +// %% main %% +// % ElementKind % +#[derive(Clone)] +pub enum ElementKind { + Item, + Page, + Entry, +} + +impl ElementKind { + pub fn kind_name(&self) -> &str { + match self { + Self::Item => "item", + Self::Page => "page", + Self::Entry => "entry", + } + } + pub fn style_file(&self, emt_name: &str) -> PathBuf { + match self { + &Self::Entry => PathBuf::from("entry.css"), + _ => PathBuf::from(format!("{}s", self.kind_name())) + .join(format!("{}.css", snake_case(emt_name))), + } + } + pub fn extra_macro_impl(&self, attr: TokenStream, item: TokenStream) -> TokenStream { + let mut handler = ElementHandler::new(attr, item); + handler.attrs.kind = self.to_owned(); + handler.handle() + } +} + +impl From for ElementKind { + fn from(lit: Lit) -> Self { + match lit { + Lit::Str(s) => match s.value().as_str() { + "entry" => ElementKind::Entry, + "page" => ElementKind::Page, + "component" => ElementKind::Item, + _ => panic!( + "Please provide a valid kind of element: options: 'Component', 'Page' and 'Entry'." + ), + }, + _ => panic!("Please provide a valid literal: options: 'Component', 'Page' and 'Entry' "), + } + } +} diff --git a/packages/macros/src/element/manifest.rs b/packages/macros/src/element/manifest.rs new file mode 100644 index 0000000..51002ae --- /dev/null +++ b/packages/macros/src/element/manifest.rs @@ -0,0 +1,50 @@ +// %%% manifest.rs %%% // +// %% includes %% +use serde::{Deserialize, Serialize}; +use std::{env, fs, path::PathBuf}; + +// %% style api %% +#[derive(Serialize, Deserialize)] +pub struct ElementConfig { + #[serde(default = "default_asset_dir")] + pub asset_dir: PathBuf, + #[serde(default = "default_bundle")] + pub bundle: bool, + #[serde(default = "default_include_in_src")] + pub include_in_src: bool, + #[serde(default = "default_page_as_entry")] + pub page_as_entry: bool, + #[serde(default = "default_component_as_entry")] + pub component_as_entry: bool, +} + +impl ElementConfig { + pub fn new() -> Self { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let config_path = PathBuf::from(manifest_dir).join("Elements.toml"); + let config_content = fs::read_to_string(config_path).unwrap_or_default(); + toml::from_str(&config_content) + .expect("Couldn't parse the toml from the elements.toml file") + } +} + +// % defaults % +fn default_bundle() -> bool { + true +} + +fn default_include_in_src() -> bool { + true +} + +fn default_asset_dir() -> PathBuf { + PathBuf::from("assets/style") +} + +fn default_component_as_entry() -> bool { + false +} + +fn default_page_as_entry() -> bool { + false +} diff --git a/packages/macros/src/element/rsx_ast.rs b/packages/macros/src/element/rsx_ast.rs new file mode 100644 index 0000000..cb71292 --- /dev/null +++ b/packages/macros/src/element/rsx_ast.rs @@ -0,0 +1,67 @@ +// %%% rsx_body.rs %%% +// %% includes %% + +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{ + braced, + parse::{Parse, ParseStream}, + parse2, + punctuated::Punctuated, + token::Brace, + Ident, LitStr, Macro, Path, Token, +}; + +// %% main %% +// % Element % +#[derive(Clone)] +pub struct Element { + pub path: Path, + pub brace: Brace, + pub attrs: Punctuated, + pub elmts: Punctuated, +} + +impl From for Element { + fn from(mac: Macro) -> Self { + parse2(mac.tokens).expect("Macro could not be parsed!") + } +} + +impl Parse for Element { + fn parse(input: ParseStream) -> syn::Result { + let content; + Ok(Self { + path: input.parse()?, + brace: braced!(content in input), + attrs: content.parse_terminated(Attribute::parse, Token![,])?, + elmts: content.parse_terminated(Element::parse, Token![,])?, + }) + } +} + +impl ToTokens for Element { + fn to_tokens(&self, tokens: &mut TokenStream) { + todo!() + } +} + +// % Attribute % +#[derive(Clone)] +pub struct Attribute { + pub name: Ident, + pub colon: Token![:], + pub value: TokenStream, + pub comma: Option, +} + +impl Parse for Attribute { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + name: input.parse()?, + colon: input.parse()?, + value: input.parse()?, + comma: input.parse()?, + }) + } +} diff --git a/packages/macros/src/entry.rs b/packages/macros/src/entry.rs new file mode 100644 index 0000000..4a2e3df --- /dev/null +++ b/packages/macros/src/entry.rs @@ -0,0 +1,14 @@ +// %%% entry.rs %%% +// this is the extra implementation of element for the ElementKind::Entry type +// #[page] instead of #[element(type = ElementKind::Page)] + +// %% includes %% +// % intern % +use crate::element::kind::*; +// % extern % +use proc_macro2::TokenStream; +// %% main %% +// % impl % +pub fn macro_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + ElementKind::Entry {}.extra_macro_impl(attr, item) +} diff --git a/packages/macros/src/icon.rs b/packages/macros/src/icon.rs index 79268cc..6ecbf03 100644 --- a/packages/macros/src/icon.rs +++ b/packages/macros/src/icon.rs @@ -22,14 +22,14 @@ use syn::{ struct Field { member: Ident, svg: String, - colon: Token![:], + _colon: Token![:], } impl Parse for Field { fn parse(input: ParseStream) -> syn::Result { Ok(Self { member: input.parse()?, - colon: input.parse()?, + _colon: input.parse()?, svg: { let mut value = String::new(); let mut needs_space = false; @@ -81,8 +81,7 @@ pub fn macro_impl(item: TokenStream) -> TokenStream { let fields: FieldValues = parse2(item).unwrap(); let mut out: TokenStream = quote! { use dioxus::prelude::*; - } - .into(); + }; for field in fields.fields { let function_name = pascal_case(&format!("{}Icon", field.member)); let function_ident = Ident::new(&function_name, Span::call_site()); diff --git a/packages/macros/src/item.rs b/packages/macros/src/item.rs new file mode 100644 index 0000000..8e0cac3 --- /dev/null +++ b/packages/macros/src/item.rs @@ -0,0 +1,14 @@ +// %%% component.rs %%% +// this is the extra implementation of element for the ElementKind::Component type +// #[component] instead of #[element(type = ElementKind::Component)] + +// %% includes %% +// % intern % +use crate::element::kind::*; +// % extern % +use proc_macro2::TokenStream; +// %% main %% +// % impl % +pub fn macro_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + ElementKind::Item {}.extra_macro_impl(attr, item) +} diff --git a/packages/macros/src/lib.rs b/packages/macros/src/lib.rs index 4e0856b..8881deb 100644 --- a/packages/macros/src/lib.rs +++ b/packages/macros/src/lib.rs @@ -1,28 +1,50 @@ +// %%% SimpleAI macros lib.rs %%% +// %% tests %% +mod tests; + +// %% icon %% +mod icon; +#[proc_macro] +pub fn icon(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + icon::macro_impl(syn::parse_macro_input!(item)).into() +} + +// %% element %% mod element; +#[proc_macro_attribute] +pub fn element( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + element::macro_impl(syn::parse_macro_input!(attr), syn::parse_macro_input!(item)).into() +} -macro_rules! create_attr_macro { - ($name: ident) => { - mod $name; - #[proc_macro_attribute] - pub fn $name( - attr: proc_macro::TokenStream, - item: proc_macro::TokenStream, - ) -> proc_macro::TokenStream { - $name::macro_impl(syn::parse_macro_input!(attr), syn::parse_macro_input!(item)).into() - } - }; +// %% component %% +mod item; +#[proc_macro_attribute] +pub fn item( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + item::macro_impl(syn::parse_macro_input!(attr), syn::parse_macro_input!(item)).into() } -macro_rules! create_function_style_macro { - ($name: ident) => { - mod $name; - #[proc_macro] - pub fn $name(item: proc_macro::TokenStream) -> proc_macro::TokenStream { - $name::macro_impl(syn::parse_macro_input!(item)).into() - } - }; +// %% page %% +mod page; +#[proc_macro_attribute] +pub fn page( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + page::macro_impl(syn::parse_macro_input!(attr), syn::parse_macro_input!(item)).into() } -create_attr_macro!(component); -create_attr_macro!(page); -create_function_style_macro!(icon); +// %% entry %% +mod entry; +#[proc_macro_attribute] +pub fn entry( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + entry::macro_impl(syn::parse_macro_input!(attr), syn::parse_macro_input!(item)).into() +} diff --git a/packages/macros/src/page.rs b/packages/macros/src/page.rs index 7c4aaa9..f0d0c2a 100644 --- a/packages/macros/src/page.rs +++ b/packages/macros/src/page.rs @@ -1,6 +1,14 @@ -use crate::element::element; -use proc_macro2::TokenStream; +// %%% page.rs %%% +// this is the extra implementation of element for the ElementKind::Component type +// #[page] instead of #[element(type = ElementKind::Page)] -pub fn macro_impl(_attr: TokenStream, item: TokenStream) -> TokenStream { - element(item, "pages") +// %% includes %% +// % intern % +use crate::element::kind::*; +// % extern % +use proc_macro2::TokenStream; +// %% main %% +// % impl % +pub fn macro_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + ElementKind::Page {}.extra_macro_impl(attr, item) } diff --git a/packages/macros/src/tests.rs b/packages/macros/src/tests.rs new file mode 100644 index 0000000..7fd5b61 --- /dev/null +++ b/packages/macros/src/tests.rs @@ -0,0 +1,4 @@ +#[test] +fn test_element() { + macrotest::expand("tests/element/*.rs"); +} diff --git a/packages/macros/tests/element/element.expanded.rs b/packages/macros/tests/element/element.expanded.rs new file mode 100644 index 0000000..419d3cc --- /dev/null +++ b/packages/macros/tests/element/element.expanded.rs @@ -0,0 +1,294 @@ +use dioxus::prelude::*; +use simple_ai_macros::*; +#[allow(non_snake_case)] +pub fn TestElementEntry() -> Element { + { + { + dioxus_core::Element::Ok({ + fn __original_template() -> &'static dioxus_core::internal::HotReloadedTemplate { + static __ORIGINAL_TEMPLATE: ::std::sync::OnceLock< + dioxus_core::internal::HotReloadedTemplate, + > = ::std::sync::OnceLock::new(); + if __ORIGINAL_TEMPLATE.get().is_none() { + _ = __ORIGINAL_TEMPLATE + .set( + dioxus_core::internal::HotReloadedTemplate::new( + None, + ::alloc::vec::Vec::new(), + ::alloc::vec::Vec::new(), + ::alloc::vec::Vec::new(), + __TEMPLATE_ROOTS, + ), + ); + } + __ORIGINAL_TEMPLATE.get().unwrap() + } + let __template_read = { + static __NORMALIZED_FILE: &'static str = { + const PATH: &str = ::const_format::pmr::__AssertStr { + x: { + const ARGS_OSRCTFL4A: ::const_format::__str_methods::ReplaceInput = ::const_format::__str_methods::ReplaceInputConv( + "/home/sert/Documents/local/github_projects/SimpleAI/packages/macros/tests/element/element.rs", + "\\\\", + "/", + ) + .conv(); + { + const OB: &[::const_format::pmr::u8; ARGS_OSRCTFL4A + .replace_length()] = &ARGS_OSRCTFL4A.replace(); + const OS: &::const_format::pmr::str = unsafe { + { + let bytes: &'static [::const_format::pmr::u8] = OB; + let string: &'static ::const_format::pmr::str = { + ::const_format::__hidden_utils::PtrToRef { + ptr: bytes as *const [::const_format::pmr::u8] as *const str, + } + .reff + }; + string + } + }; + OS + } + }, + } + .x; + ::const_format::pmr::__AssertStr { + x: { + const ARGS_OSRCTFL4A: ::const_format::__str_methods::ReplaceInput = ::const_format::__str_methods::ReplaceInputConv( + PATH, + '\\', + "/", + ) + .conv(); + { + const OB: &[::const_format::pmr::u8; ARGS_OSRCTFL4A + .replace_length()] = &ARGS_OSRCTFL4A.replace(); + const OS: &::const_format::pmr::str = unsafe { + { + let bytes: &'static [::const_format::pmr::u8] = OB; + let string: &'static ::const_format::pmr::str = { + ::const_format::__hidden_utils::PtrToRef { + ptr: bytes as *const [::const_format::pmr::u8] as *const str, + } + .reff + }; + string + } + }; + OS + } + }, + } + .x + }; + static __TEMPLATE: GlobalSignal< + Option, + > = GlobalSignal::with_location( + || None::, + __NORMALIZED_FILE, + 4u32, + 1u32, + 0usize, + ); + dioxus_core::Runtime::current().ok().map(|_| __TEMPLATE.read()) + }; + let __template_read = match __template_read + .as_ref() + .map(|__template_read| __template_read.as_ref()) + { + Some(Some(__template_read)) => &__template_read, + _ => __original_template(), + }; + let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new( + ::alloc::vec::Vec::new(), + ); + let __dynamic_nodes: [dioxus_core::DynamicNode; 0usize] = []; + let __dynamic_attributes: [Box<[dioxus_core::Attribute]>; 0usize] = []; + #[doc(hidden)] + static __TEMPLATE_ROOTS: &[dioxus_core::TemplateNode] = &[ + { + dioxus_core::TemplateNode::Element { + tag: dioxus_elements::elements::main::TAG_NAME, + namespace: dioxus_elements::main::NAME_SPACE, + attrs: &[ + dioxus_core::TemplateAttribute::Static { + name: dioxus_elements::main::class.0, + namespace: dioxus_elements::main::class.1, + value: "TestElementEntry", + }, + ], + children: &[], + } + }, + ]; + { + let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new( + Vec::from(__dynamic_nodes), + Vec::from(__dynamic_attributes), + __dynamic_literal_pool, + ); + __dynamic_value_pool.render_with(__template_read) + } + }) + } + } +} +#[allow(non_snake_case)] +#[doc(hidden)] +mod TestElementEntry_completions { + #[doc(hidden)] + #[allow(non_camel_case_types)] + /// This enum is generated to help autocomplete the braces after the component. It does nothing + pub enum Component { + TestElementEntry {}, + } +} +#[allow(unused)] +pub use TestElementEntry_completions::Component::TestElementEntry; +#[allow(non_snake_case)] +pub fn TestElementPage() -> Element { + { + { + dioxus_core::Element::Ok({ + fn __original_template() -> &'static dioxus_core::internal::HotReloadedTemplate { + static __ORIGINAL_TEMPLATE: ::std::sync::OnceLock< + dioxus_core::internal::HotReloadedTemplate, + > = ::std::sync::OnceLock::new(); + if __ORIGINAL_TEMPLATE.get().is_none() { + _ = __ORIGINAL_TEMPLATE + .set( + dioxus_core::internal::HotReloadedTemplate::new( + None, + ::alloc::vec::Vec::new(), + ::alloc::vec::Vec::new(), + ::alloc::vec::Vec::new(), + __TEMPLATE_ROOTS, + ), + ); + } + __ORIGINAL_TEMPLATE.get().unwrap() + } + let __template_read = { + static __NORMALIZED_FILE: &'static str = { + const PATH: &str = ::const_format::pmr::__AssertStr { + x: { + const ARGS_OSRCTFL4A: ::const_format::__str_methods::ReplaceInput = ::const_format::__str_methods::ReplaceInputConv( + "/home/sert/Documents/local/github_projects/SimpleAI/packages/macros/tests/element/element.rs", + "\\\\", + "/", + ) + .conv(); + { + const OB: &[::const_format::pmr::u8; ARGS_OSRCTFL4A + .replace_length()] = &ARGS_OSRCTFL4A.replace(); + const OS: &::const_format::pmr::str = unsafe { + { + let bytes: &'static [::const_format::pmr::u8] = OB; + let string: &'static ::const_format::pmr::str = { + ::const_format::__hidden_utils::PtrToRef { + ptr: bytes as *const [::const_format::pmr::u8] as *const str, + } + .reff + }; + string + } + }; + OS + } + }, + } + .x; + ::const_format::pmr::__AssertStr { + x: { + const ARGS_OSRCTFL4A: ::const_format::__str_methods::ReplaceInput = ::const_format::__str_methods::ReplaceInputConv( + PATH, + '\\', + "/", + ) + .conv(); + { + const OB: &[::const_format::pmr::u8; ARGS_OSRCTFL4A + .replace_length()] = &ARGS_OSRCTFL4A.replace(); + const OS: &::const_format::pmr::str = unsafe { + { + let bytes: &'static [::const_format::pmr::u8] = OB; + let string: &'static ::const_format::pmr::str = { + ::const_format::__hidden_utils::PtrToRef { + ptr: bytes as *const [::const_format::pmr::u8] as *const str, + } + .reff + }; + string + } + }; + OS + } + }, + } + .x + }; + static __TEMPLATE: GlobalSignal< + Option, + > = GlobalSignal::with_location( + || None::, + __NORMALIZED_FILE, + 20u32, + 1u32, + 0usize, + ); + dioxus_core::Runtime::current().ok().map(|_| __TEMPLATE.read()) + }; + let __template_read = match __template_read + .as_ref() + .map(|__template_read| __template_read.as_ref()) + { + Some(Some(__template_read)) => &__template_read, + _ => __original_template(), + }; + let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new( + ::alloc::vec::Vec::new(), + ); + let __dynamic_nodes: [dioxus_core::DynamicNode; 0usize] = []; + let __dynamic_attributes: [Box<[dioxus_core::Attribute]>; 0usize] = []; + #[doc(hidden)] + static __TEMPLATE_ROOTS: &[dioxus_core::TemplateNode] = &[ + { + dioxus_core::TemplateNode::Element { + tag: dioxus_elements::elements::main::TAG_NAME, + namespace: dioxus_elements::main::NAME_SPACE, + attrs: &[ + dioxus_core::TemplateAttribute::Static { + name: dioxus_elements::main::class.0, + namespace: dioxus_elements::main::class.1, + value: "TestElementPage", + }, + ], + children: &[], + } + }, + ]; + { + let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new( + Vec::from(__dynamic_nodes), + Vec::from(__dynamic_attributes), + __dynamic_literal_pool, + ); + __dynamic_value_pool.render_with(__template_read) + } + }) + } + } +} +#[allow(non_snake_case)] +#[doc(hidden)] +mod TestElementPage_completions { + #[doc(hidden)] + #[allow(non_camel_case_types)] + /// This enum is generated to help autocomplete the braces after the component. It does nothing + pub enum Component { + TestElementPage {}, + } +} +#[allow(unused)] +pub use TestElementPage_completions::Component::TestElementPage; diff --git a/packages/macros/tests/element/element.rs b/packages/macros/tests/element/element.rs new file mode 100644 index 0000000..70da265 --- /dev/null +++ b/packages/macros/tests/element/element.rs @@ -0,0 +1,26 @@ +use dioxus::prelude::*; +use simple_ai_macros::*; + +#[element(kind = "entry", no_css = true)] +pub fn TestElementEntry() -> Element { + rsx! { + main { + } + } +} + +#[element(kind = "item", no_css = true)] +pub fn TestElementItem() -> Element { + rsx! { + main { + } + } +} + +#[element(kind = "page", no_css = true)] +pub fn TestElementPage() -> Element { + rsx! { + main { + } + } +} diff --git a/packages/macros/tests/element/entry.expanded.rs b/packages/macros/tests/element/entry.expanded.rs new file mode 100644 index 0000000..be5b362 --- /dev/null +++ b/packages/macros/tests/element/entry.expanded.rs @@ -0,0 +1,148 @@ +use dioxus::prelude::*; +use simple_ai_macros::*; +#[allow(non_snake_case)] +pub fn TestEntry() -> Element { + { + { + dioxus_core::Element::Ok({ + fn __original_template() -> &'static dioxus_core::internal::HotReloadedTemplate { + static __ORIGINAL_TEMPLATE: ::std::sync::OnceLock< + dioxus_core::internal::HotReloadedTemplate, + > = ::std::sync::OnceLock::new(); + if __ORIGINAL_TEMPLATE.get().is_none() { + _ = __ORIGINAL_TEMPLATE + .set( + dioxus_core::internal::HotReloadedTemplate::new( + None, + ::alloc::vec::Vec::new(), + ::alloc::vec::Vec::new(), + ::alloc::vec::Vec::new(), + __TEMPLATE_ROOTS, + ), + ); + } + __ORIGINAL_TEMPLATE.get().unwrap() + } + let __template_read = { + static __NORMALIZED_FILE: &'static str = { + const PATH: &str = ::const_format::pmr::__AssertStr { + x: { + const ARGS_OSRCTFL4A: ::const_format::__str_methods::ReplaceInput = ::const_format::__str_methods::ReplaceInputConv( + "/home/sert/Documents/local/github_projects/SimpleAI/packages/macros/tests/element/entry.rs", + "\\\\", + "/", + ) + .conv(); + { + const OB: &[::const_format::pmr::u8; ARGS_OSRCTFL4A + .replace_length()] = &ARGS_OSRCTFL4A.replace(); + const OS: &::const_format::pmr::str = unsafe { + { + let bytes: &'static [::const_format::pmr::u8] = OB; + let string: &'static ::const_format::pmr::str = { + ::const_format::__hidden_utils::PtrToRef { + ptr: bytes as *const [::const_format::pmr::u8] as *const str, + } + .reff + }; + string + } + }; + OS + } + }, + } + .x; + ::const_format::pmr::__AssertStr { + x: { + const ARGS_OSRCTFL4A: ::const_format::__str_methods::ReplaceInput = ::const_format::__str_methods::ReplaceInputConv( + PATH, + '\\', + "/", + ) + .conv(); + { + const OB: &[::const_format::pmr::u8; ARGS_OSRCTFL4A + .replace_length()] = &ARGS_OSRCTFL4A.replace(); + const OS: &::const_format::pmr::str = unsafe { + { + let bytes: &'static [::const_format::pmr::u8] = OB; + let string: &'static ::const_format::pmr::str = { + ::const_format::__hidden_utils::PtrToRef { + ptr: bytes as *const [::const_format::pmr::u8] as *const str, + } + .reff + }; + string + } + }; + OS + } + }, + } + .x + }; + static __TEMPLATE: GlobalSignal< + Option, + > = GlobalSignal::with_location( + || None::, + __NORMALIZED_FILE, + 4u32, + 1u32, + 0usize, + ); + dioxus_core::Runtime::current().ok().map(|_| __TEMPLATE.read()) + }; + let __template_read = match __template_read + .as_ref() + .map(|__template_read| __template_read.as_ref()) + { + Some(Some(__template_read)) => &__template_read, + _ => __original_template(), + }; + let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new( + ::alloc::vec::Vec::new(), + ); + let __dynamic_nodes: [dioxus_core::DynamicNode; 0usize] = []; + let __dynamic_attributes: [Box<[dioxus_core::Attribute]>; 0usize] = []; + #[doc(hidden)] + static __TEMPLATE_ROOTS: &[dioxus_core::TemplateNode] = &[ + { + dioxus_core::TemplateNode::Element { + tag: dioxus_elements::elements::main::TAG_NAME, + namespace: dioxus_elements::main::NAME_SPACE, + attrs: &[ + dioxus_core::TemplateAttribute::Static { + name: dioxus_elements::main::class.0, + namespace: dioxus_elements::main::class.1, + value: "TestEntry", + }, + ], + children: &[], + } + }, + ]; + { + let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new( + Vec::from(__dynamic_nodes), + Vec::from(__dynamic_attributes), + __dynamic_literal_pool, + ); + __dynamic_value_pool.render_with(__template_read) + } + }) + } + } +} +#[allow(non_snake_case)] +#[doc(hidden)] +mod TestEntry_completions { + #[doc(hidden)] + #[allow(non_camel_case_types)] + /// This enum is generated to help autocomplete the braces after the component. It does nothing + pub enum Component { + TestEntry {}, + } +} +#[allow(unused)] +pub use TestEntry_completions::Component::TestEntry; diff --git a/packages/macros/tests/element/entry.rs b/packages/macros/tests/element/entry.rs new file mode 100644 index 0000000..17bd52f --- /dev/null +++ b/packages/macros/tests/element/entry.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +use simple_ai_macros::*; + +#[entry(no_css = true)] +pub fn TestEntry() -> Element { + rsx! { + main { + } + } +} diff --git a/packages/macros/tests/element/item.expanded.rs b/packages/macros/tests/element/item.expanded.rs new file mode 100644 index 0000000..65cfc57 --- /dev/null +++ b/packages/macros/tests/element/item.expanded.rs @@ -0,0 +1,148 @@ +use dioxus::prelude::*; +use simple_ai_macros::*; +#[allow(non_snake_case)] +pub fn TestItem() -> Element { + { + { + dioxus_core::Element::Ok({ + fn __original_template() -> &'static dioxus_core::internal::HotReloadedTemplate { + static __ORIGINAL_TEMPLATE: ::std::sync::OnceLock< + dioxus_core::internal::HotReloadedTemplate, + > = ::std::sync::OnceLock::new(); + if __ORIGINAL_TEMPLATE.get().is_none() { + _ = __ORIGINAL_TEMPLATE + .set( + dioxus_core::internal::HotReloadedTemplate::new( + None, + ::alloc::vec::Vec::new(), + ::alloc::vec::Vec::new(), + ::alloc::vec::Vec::new(), + __TEMPLATE_ROOTS, + ), + ); + } + __ORIGINAL_TEMPLATE.get().unwrap() + } + let __template_read = { + static __NORMALIZED_FILE: &'static str = { + const PATH: &str = ::const_format::pmr::__AssertStr { + x: { + const ARGS_OSRCTFL4A: ::const_format::__str_methods::ReplaceInput = ::const_format::__str_methods::ReplaceInputConv( + "/home/sert/Documents/local/github_projects/SimpleAI/packages/macros/tests/element/item.rs", + "\\\\", + "/", + ) + .conv(); + { + const OB: &[::const_format::pmr::u8; ARGS_OSRCTFL4A + .replace_length()] = &ARGS_OSRCTFL4A.replace(); + const OS: &::const_format::pmr::str = unsafe { + { + let bytes: &'static [::const_format::pmr::u8] = OB; + let string: &'static ::const_format::pmr::str = { + ::const_format::__hidden_utils::PtrToRef { + ptr: bytes as *const [::const_format::pmr::u8] as *const str, + } + .reff + }; + string + } + }; + OS + } + }, + } + .x; + ::const_format::pmr::__AssertStr { + x: { + const ARGS_OSRCTFL4A: ::const_format::__str_methods::ReplaceInput = ::const_format::__str_methods::ReplaceInputConv( + PATH, + '\\', + "/", + ) + .conv(); + { + const OB: &[::const_format::pmr::u8; ARGS_OSRCTFL4A + .replace_length()] = &ARGS_OSRCTFL4A.replace(); + const OS: &::const_format::pmr::str = unsafe { + { + let bytes: &'static [::const_format::pmr::u8] = OB; + let string: &'static ::const_format::pmr::str = { + ::const_format::__hidden_utils::PtrToRef { + ptr: bytes as *const [::const_format::pmr::u8] as *const str, + } + .reff + }; + string + } + }; + OS + } + }, + } + .x + }; + static __TEMPLATE: GlobalSignal< + Option, + > = GlobalSignal::with_location( + || None::, + __NORMALIZED_FILE, + 4u32, + 1u32, + 0usize, + ); + dioxus_core::Runtime::current().ok().map(|_| __TEMPLATE.read()) + }; + let __template_read = match __template_read + .as_ref() + .map(|__template_read| __template_read.as_ref()) + { + Some(Some(__template_read)) => &__template_read, + _ => __original_template(), + }; + let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new( + ::alloc::vec::Vec::new(), + ); + let __dynamic_nodes: [dioxus_core::DynamicNode; 0usize] = []; + let __dynamic_attributes: [Box<[dioxus_core::Attribute]>; 0usize] = []; + #[doc(hidden)] + static __TEMPLATE_ROOTS: &[dioxus_core::TemplateNode] = &[ + { + dioxus_core::TemplateNode::Element { + tag: dioxus_elements::elements::main::TAG_NAME, + namespace: dioxus_elements::main::NAME_SPACE, + attrs: &[ + dioxus_core::TemplateAttribute::Static { + name: dioxus_elements::main::class.0, + namespace: dioxus_elements::main::class.1, + value: "TestItem", + }, + ], + children: &[], + } + }, + ]; + { + let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new( + Vec::from(__dynamic_nodes), + Vec::from(__dynamic_attributes), + __dynamic_literal_pool, + ); + __dynamic_value_pool.render_with(__template_read) + } + }) + } + } +} +#[allow(non_snake_case)] +#[doc(hidden)] +mod TestItem_completions { + #[doc(hidden)] + #[allow(non_camel_case_types)] + /// This enum is generated to help autocomplete the braces after the component. It does nothing + pub enum Component { + TestItem {}, + } +} +#[allow(unused)] +pub use TestItem_completions::Component::TestItem; diff --git a/packages/macros/tests/element/item.rs b/packages/macros/tests/element/item.rs new file mode 100644 index 0000000..5d252be --- /dev/null +++ b/packages/macros/tests/element/item.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +use simple_ai_macros::*; + +#[item(no_css = true)] +pub fn TestItem() -> Element { + rsx! { + main { + } + } +} diff --git a/packages/macros/tests/element/item_dx_element.expanded.rs b/packages/macros/tests/element/item_dx_element.expanded.rs new file mode 100644 index 0000000..58d4a4d --- /dev/null +++ b/packages/macros/tests/element/item_dx_element.expanded.rs @@ -0,0 +1,2 @@ +use dioxus::prelude::*; +use simple_ai_macros::*; diff --git a/packages/macros/tests/element/item_dx_element.rs b/packages/macros/tests/element/item_dx_element.rs new file mode 100644 index 0000000..be96c94 --- /dev/null +++ b/packages/macros/tests/element/item_dx_element.rs @@ -0,0 +1,9 @@ +use dioxus::prelude::*; +use simple_ai_macros::*; + +#[item(no_css = true)] +pub fn TestItemDxElement() -> Element { + rsx! { + Link { to: Route::Index {} } + } +} diff --git a/packages/macros/tests/element/page.expanded.rs b/packages/macros/tests/element/page.expanded.rs new file mode 100644 index 0000000..b5653b3 --- /dev/null +++ b/packages/macros/tests/element/page.expanded.rs @@ -0,0 +1,148 @@ +use dioxus::prelude::*; +use simple_ai_macros::*; +#[allow(non_snake_case)] +pub fn TestPage() -> Element { + { + { + dioxus_core::Element::Ok({ + fn __original_template() -> &'static dioxus_core::internal::HotReloadedTemplate { + static __ORIGINAL_TEMPLATE: ::std::sync::OnceLock< + dioxus_core::internal::HotReloadedTemplate, + > = ::std::sync::OnceLock::new(); + if __ORIGINAL_TEMPLATE.get().is_none() { + _ = __ORIGINAL_TEMPLATE + .set( + dioxus_core::internal::HotReloadedTemplate::new( + None, + ::alloc::vec::Vec::new(), + ::alloc::vec::Vec::new(), + ::alloc::vec::Vec::new(), + __TEMPLATE_ROOTS, + ), + ); + } + __ORIGINAL_TEMPLATE.get().unwrap() + } + let __template_read = { + static __NORMALIZED_FILE: &'static str = { + const PATH: &str = ::const_format::pmr::__AssertStr { + x: { + const ARGS_OSRCTFL4A: ::const_format::__str_methods::ReplaceInput = ::const_format::__str_methods::ReplaceInputConv( + "/home/sert/Documents/local/github_projects/SimpleAI/packages/macros/tests/element/page.rs", + "\\\\", + "/", + ) + .conv(); + { + const OB: &[::const_format::pmr::u8; ARGS_OSRCTFL4A + .replace_length()] = &ARGS_OSRCTFL4A.replace(); + const OS: &::const_format::pmr::str = unsafe { + { + let bytes: &'static [::const_format::pmr::u8] = OB; + let string: &'static ::const_format::pmr::str = { + ::const_format::__hidden_utils::PtrToRef { + ptr: bytes as *const [::const_format::pmr::u8] as *const str, + } + .reff + }; + string + } + }; + OS + } + }, + } + .x; + ::const_format::pmr::__AssertStr { + x: { + const ARGS_OSRCTFL4A: ::const_format::__str_methods::ReplaceInput = ::const_format::__str_methods::ReplaceInputConv( + PATH, + '\\', + "/", + ) + .conv(); + { + const OB: &[::const_format::pmr::u8; ARGS_OSRCTFL4A + .replace_length()] = &ARGS_OSRCTFL4A.replace(); + const OS: &::const_format::pmr::str = unsafe { + { + let bytes: &'static [::const_format::pmr::u8] = OB; + let string: &'static ::const_format::pmr::str = { + ::const_format::__hidden_utils::PtrToRef { + ptr: bytes as *const [::const_format::pmr::u8] as *const str, + } + .reff + }; + string + } + }; + OS + } + }, + } + .x + }; + static __TEMPLATE: GlobalSignal< + Option, + > = GlobalSignal::with_location( + || None::, + __NORMALIZED_FILE, + 4u32, + 1u32, + 0usize, + ); + dioxus_core::Runtime::current().ok().map(|_| __TEMPLATE.read()) + }; + let __template_read = match __template_read + .as_ref() + .map(|__template_read| __template_read.as_ref()) + { + Some(Some(__template_read)) => &__template_read, + _ => __original_template(), + }; + let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new( + ::alloc::vec::Vec::new(), + ); + let __dynamic_nodes: [dioxus_core::DynamicNode; 0usize] = []; + let __dynamic_attributes: [Box<[dioxus_core::Attribute]>; 0usize] = []; + #[doc(hidden)] + static __TEMPLATE_ROOTS: &[dioxus_core::TemplateNode] = &[ + { + dioxus_core::TemplateNode::Element { + tag: dioxus_elements::elements::main::TAG_NAME, + namespace: dioxus_elements::main::NAME_SPACE, + attrs: &[ + dioxus_core::TemplateAttribute::Static { + name: dioxus_elements::main::class.0, + namespace: dioxus_elements::main::class.1, + value: "TestPage", + }, + ], + children: &[], + } + }, + ]; + { + let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new( + Vec::from(__dynamic_nodes), + Vec::from(__dynamic_attributes), + __dynamic_literal_pool, + ); + __dynamic_value_pool.render_with(__template_read) + } + }) + } + } +} +#[allow(non_snake_case)] +#[doc(hidden)] +mod TestPage_completions { + #[doc(hidden)] + #[allow(non_camel_case_types)] + /// This enum is generated to help autocomplete the braces after the component. It does nothing + pub enum Component { + TestPage {}, + } +} +#[allow(unused)] +pub use TestPage_completions::Component::TestPage; diff --git a/packages/macros/tests/element/page.rs b/packages/macros/tests/element/page.rs new file mode 100644 index 0000000..65d6424 --- /dev/null +++ b/packages/macros/tests/element/page.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +use simple_ai_macros::*; + +#[page(no_css = true)] +pub fn TestPage() -> Element { + rsx! { + main { + } + } +} diff --git a/packages/platforms/desktop/Cargo.toml b/packages/platforms/desktop/Cargo.toml index 12e8ff2..c84c194 100644 --- a/packages/platforms/desktop/Cargo.toml +++ b/packages/platforms/desktop/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" [dependencies] dioxus = { workspace = true, features = ["desktop"]} simple-ai-frontend = { workspace = true } +simple-ai-macros = { workspace = true } diff --git a/packages/platforms/desktop/src/router.rs b/packages/platforms/desktop/src/router.rs index b12d7f4..9112f15 100644 --- a/packages/platforms/desktop/src/router.rs +++ b/packages/platforms/desktop/src/router.rs @@ -1,4 +1,5 @@ use crate::prelude::{pages::prelude::*, *}; +use simple_ai_macros::entry; fn StartPage() -> Element { rsx! { @@ -11,15 +12,21 @@ fn StartPage() -> Element { } fn SearchPage() -> Element { - rsx! { Search {} } + rsx! { + Search {} + } } fn NewPage() -> Element { - rsx! { New {} } + rsx! { + New {} + } } fn EditorPage() -> Element { - rsx! { Editor {} } + rsx! { + Editor {} + } } #[derive(Debug, Clone, Routable, PartialEq)] From 46ec25b0f904afc1b605aada9498b8f2672bff13 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 12 Oct 2025 23:04:00 +0200 Subject: [PATCH 04/17] macros working | breadcrumbs --- packages/frontend/assets/style/entry.css | 6 ++ .../assets/style/items/breadcrumbs.css | 19 ++++ .../assets/style/items/nav_button.css | 6 ++ .../frontend/assets/style/pages/projects.css | 6 ++ .../frontend/assets/style/pages/start.css | 9 +- packages/frontend/src/lib.rs | 2 + packages/frontend/src/modules.rs | 2 +- packages/frontend/src/modules/components.rs | 3 +- .../src/modules/components/breadcrumbs.rs | 30 +++++++ .../src/modules/components/nav_button.rs | 13 ++- packages/frontend/src/modules/icons.rs | 5 +- packages/frontend/src/modules/pages.rs | 2 + packages/frontend/src/modules/pages/new.rs | 2 +- .../frontend/src/modules/pages/projects.rs | 16 ++++ packages/frontend/src/modules/pages/start.rs | 21 ++--- packages/frontend/src/modules/router.rs | 30 +++++++ packages/frontend/src/modules/utils.rs | 8 -- packages/macros/src/element/function.rs | 72 ++++++++-------- packages/macros/src/element/handler.rs | 56 +++++++----- packages/macros/src/element/rsx_ast.rs | 86 +++++++++++++++---- packages/platforms/desktop/src/main.rs | 5 +- packages/platforms/desktop/src/router.rs | 44 ---------- packages/platforms/web/src/main.rs | 10 +-- packages/platforms/web/src/router.rs | 37 -------- 24 files changed, 290 insertions(+), 200 deletions(-) create mode 100644 packages/frontend/assets/style/items/breadcrumbs.css create mode 100644 packages/frontend/assets/style/pages/projects.css create mode 100644 packages/frontend/src/modules/components/breadcrumbs.rs create mode 100644 packages/frontend/src/modules/pages/projects.rs create mode 100644 packages/frontend/src/modules/router.rs delete mode 100644 packages/frontend/src/modules/utils.rs delete mode 100644 packages/platforms/desktop/src/router.rs delete mode 100644 packages/platforms/web/src/router.rs diff --git a/packages/frontend/assets/style/entry.css b/packages/frontend/assets/style/entry.css index f6ad793..f48137c 100644 --- a/packages/frontend/assets/style/entry.css +++ b/packages/frontend/assets/style/entry.css @@ -100,6 +100,12 @@ a { color: rgb(var(--text-color)); } +/* % h1 % */ +h1 { + padding: 0; + margin: 0; +} + /* % form % */ form { align-items: center; diff --git a/packages/frontend/assets/style/items/breadcrumbs.css b/packages/frontend/assets/style/items/breadcrumbs.css new file mode 100644 index 0000000..c1072f0 --- /dev/null +++ b/packages/frontend/assets/style/items/breadcrumbs.css @@ -0,0 +1,19 @@ +.Breadcrumbs { + width: 100%; + display: flex; + flex-direction: row; + justify-content: left; + align-items: center; + padding: 1rem; + box-sizing: content-box; + background-color: rgba(var(--color-2), 0.3); +} + +.Breadcrumbs .HomeIcon { + scale: 0.7; +} + +.Breadcrumbs .Icon { + padding: 0; + margin: 0; +} diff --git a/packages/frontend/assets/style/items/nav_button.css b/packages/frontend/assets/style/items/nav_button.css index e69de29..e8e9876 100644 --- a/packages/frontend/assets/style/items/nav_button.css +++ b/packages/frontend/assets/style/items/nav_button.css @@ -0,0 +1,6 @@ +.NavButton { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; +} diff --git a/packages/frontend/assets/style/pages/projects.css b/packages/frontend/assets/style/pages/projects.css new file mode 100644 index 0000000..99d45ac --- /dev/null +++ b/packages/frontend/assets/style/pages/projects.css @@ -0,0 +1,6 @@ +.Projects { + padding: 2rem; + display: flex; + flex-direction: column; + gap: 1rem; +} diff --git a/packages/frontend/assets/style/pages/start.css b/packages/frontend/assets/style/pages/start.css index 16da738..ea4dfcf 100644 --- a/packages/frontend/assets/style/pages/start.css +++ b/packages/frontend/assets/style/pages/start.css @@ -10,10 +10,11 @@ div .button-container { display: flex; flex-direction: row; align-items: center; - justify-content: space-between; + justify-content: space-around; } -.NavbarButton .Icon { - height: 3rem; - width: 3rem; +.NavButton .Icon { + border-radius: 4rem; + border-style: solid; + border-width: 0.2rem; } diff --git a/packages/frontend/src/lib.rs b/packages/frontend/src/lib.rs index c6caddf..a7271ae 100644 --- a/packages/frontend/src/lib.rs +++ b/packages/frontend/src/lib.rs @@ -18,6 +18,8 @@ pub(crate) mod utils { pub use simple_ai_macros::*; // % icons % pub use super::modules::icons::*; + // % router % + pub use crate::modules::router::prelude::*; // % custom types % pub type PageVector = Vector2D; } diff --git a/packages/frontend/src/modules.rs b/packages/frontend/src/modules.rs index e1410e7..1dfcb42 100644 --- a/packages/frontend/src/modules.rs +++ b/packages/frontend/src/modules.rs @@ -4,4 +4,4 @@ pub mod components; pub mod icons; pub mod pages; -pub mod utils; +pub mod router; diff --git a/packages/frontend/src/modules/components.rs b/packages/frontend/src/modules/components.rs index 241bf4e..87c3a4f 100644 --- a/packages/frontend/src/modules/components.rs +++ b/packages/frontend/src/modules/components.rs @@ -1,6 +1,7 @@ // %%% components.rs %%% // %% exports %% +pub mod breadcrumbs; pub mod connection; pub mod divider; pub mod drag_area; @@ -19,6 +20,7 @@ pub mod viewport; // %% prelude %% pub mod prelude { + pub use super::breadcrumbs::*; pub use super::divider::*; pub use super::drag_area::*; pub use super::draggable::*; @@ -40,7 +42,6 @@ pub mod prelude { // %% utils %% pub(crate) mod utils { - pub use crate::modules::utils::*; pub use crate::utils::*; pub use simple_ai_backend::prelude::*; } diff --git a/packages/frontend/src/modules/components/breadcrumbs.rs b/packages/frontend/src/modules/components/breadcrumbs.rs new file mode 100644 index 0000000..066b674 --- /dev/null +++ b/packages/frontend/src/modules/components/breadcrumbs.rs @@ -0,0 +1,30 @@ +// %%% components / breadcrumbs.rs %%% +/// The top navigation which shows you where you are. +// +// %% includes %% +use super::utils::*; + +// %% main %% +#[item] +pub fn Breadcrumbs() -> Element { + let route_str = router().full_route_string(); + let mut crumbs: Vec<&str> = route_str.rsplit_terminator("/").collect(); + crumbs.pop(); + let mut assembled = String::new(); + rsx! { + div { + Link { to: Route::Start {}, HomeIcon {} } + for crumb in crumbs.iter() { + RightBracketIcon {} + Link { + to: { + assembled.push('/'); + assembled.push_str(crumb); + assembled.parse::>().unwrap() + }, + {crumb.to_string()} + } + } + } + } +} diff --git a/packages/frontend/src/modules/components/nav_button.rs b/packages/frontend/src/modules/components/nav_button.rs index 1ffe431..7cd3f2e 100644 --- a/packages/frontend/src/modules/components/nav_button.rs +++ b/packages/frontend/src/modules/components/nav_button.rs @@ -2,7 +2,7 @@ // %% includes %% use super::utils::*; -use dioxus::router::NavigationTarget; +use dioxus::{core::AttributeValue, router::NavigationTarget}; // %% main %% @@ -12,9 +12,14 @@ pub fn NavButton( #[props(into)] to: NavigationTarget, #[props(extends = GlobalAttributes)] attributes: Vec, ) -> Element { - rsx! { - Link { to, class: "asdf", - div { ..attributes,{children} } + let mut other_classes = String::new(); + if let Some(pos) = attributes.iter().position(|x| x.name == "class") { + let value = attributes.remove(pos).value; + if let AttributeValue::Text(text) = value { + other_classes = text; } } + rsx! { + Link { class: "{other_classes}", to, attributes, {children} } + } } diff --git a/packages/frontend/src/modules/icons.rs b/packages/frontend/src/modules/icons.rs index 175dbaa..35feb4a 100644 --- a/packages/frontend/src/modules/icons.rs +++ b/packages/frontend/src/modules/icons.rs @@ -5,8 +5,11 @@ use crate::utils::*; // %% main %% icon! { + right_bracket: , + home: , + projects: , search: , - new: , + new: , editor: , market: } diff --git a/packages/frontend/src/modules/pages.rs b/packages/frontend/src/modules/pages.rs index b082465..4244c2d 100644 --- a/packages/frontend/src/modules/pages.rs +++ b/packages/frontend/src/modules/pages.rs @@ -3,6 +3,7 @@ // %% exports %% pub mod editor; pub mod new; +pub mod projects; pub mod search; pub mod start; @@ -10,6 +11,7 @@ pub mod start; pub mod prelude { pub use super::editor::Editor; pub use super::new::New; + pub use super::projects::Projects; pub use super::search::Search; pub use super::start::Start; } diff --git a/packages/frontend/src/modules/pages/new.rs b/packages/frontend/src/modules/pages/new.rs index ea9f1f8..810679b 100644 --- a/packages/frontend/src/modules/pages/new.rs +++ b/packages/frontend/src/modules/pages/new.rs @@ -4,7 +4,7 @@ use super::utils::*; // %% main %% -#[component] +#[page] pub fn New() -> Element { rsx! { main { diff --git a/packages/frontend/src/modules/pages/projects.rs b/packages/frontend/src/modules/pages/projects.rs new file mode 100644 index 0000000..f679566 --- /dev/null +++ b/packages/frontend/src/modules/pages/projects.rs @@ -0,0 +1,16 @@ +// %%% pages / start.rs %%% + +// %% includes %% +use super::utils::*; +use dioxus::router::NavigationTarget; + +// %% main %% +#[page] +pub fn Projects() -> Element { + rsx! { + main { + h1 { class: "projects-heading", "Projects" } + div { class: "projects-view" } + } + } +} diff --git a/packages/frontend/src/modules/pages/start.rs b/packages/frontend/src/modules/pages/start.rs index 15894e0..a454dbc 100644 --- a/packages/frontend/src/modules/pages/start.rs +++ b/packages/frontend/src/modules/pages/start.rs @@ -2,29 +2,20 @@ // %% includes %% use super::utils::*; -use dioxus::router::NavigationTarget; // %% main %% #[page] -pub fn Start( - #[props(into)] search_route: NavigationTarget, - #[props(into)] new_route: NavigationTarget, - #[props(into)] editor_route: NavigationTarget, -) -> Element { +pub fn Start() -> Element { rsx! { main { div { class: "button-container", - NavButton { class: "search", to: search_route, - SearchIcon {} - p { "search" } + NavButton { class: "editor", to: Route::Projects {}, + ProjectsIcon {} + p { "projects" } } - NavButton { class: "new", to: new_route, + NavButton { class: "new", to: Route::New {}, NewIcon {} - p { "new" } - } - NavButton { class: "editor", to: editor_route, - EditorIcon {} - p { "editor" } + p { "new project" } } } } diff --git a/packages/frontend/src/modules/router.rs b/packages/frontend/src/modules/router.rs new file mode 100644 index 0000000..5a98c52 --- /dev/null +++ b/packages/frontend/src/modules/router.rs @@ -0,0 +1,30 @@ +pub mod core { + use super::super::pages::prelude::*; + use crate::{modules::pages::utils::Breadcrumbs, utils::*}; + + fn TopNav() -> Element { + rsx! { + Breadcrumbs {} + Outlet:: {} + } + } + + #[derive(Debug, Clone, Routable, PartialEq)] + #[rustfmt::skip] + pub enum Route { + #[layout(TopNav)] + #[route("/")] + Start {}, + #[nest("/projects")] + #[route("/")] + Projects {}, + #[end_nest] + #[nest("/new")] + #[route("/")] + New {}, + } +} + +pub mod prelude { + pub use super::core::Route; +} diff --git a/packages/frontend/src/modules/utils.rs b/packages/frontend/src/modules/utils.rs deleted file mode 100644 index f560554..0000000 --- a/packages/frontend/src/modules/utils.rs +++ /dev/null @@ -1,8 +0,0 @@ -use dioxus::dioxus_core::{Attribute, AttributeValue}; -pub fn add_to_classes(class: &str, attributes: &mut Vec) { - for a in attributes { - if a.name == "class" { - a.value = AttributeValue::Text(format!("{} {:?}", class, a.value)); - } - } -} diff --git a/packages/macros/src/element/function.rs b/packages/macros/src/element/function.rs index 61657ad..20ed005 100644 --- a/packages/macros/src/element/function.rs +++ b/packages/macros/src/element/function.rs @@ -1,66 +1,62 @@ +use crate::element::function; + // %%% function.rs %%% // %% includes %% +// % intern % +use super::rsx_ast::Element; // % extern % -use change_case::*; +use proc_macro2::TokenStream; +use quote::ToTokens; use syn::{ parse::{Parse, ParseStream}, - ItemFn, Macro, Stmt, + parse2, ItemFn, Macro, Stmt, StmtMacro, }; // % intern % // %% main %% // % Element Function % +#[derive(Clone)] pub struct ElementFunction { pub function: ItemFn, pub name: String, - pub element_name: String, + pub macro_ast: Element, } impl ElementFunction { - fn extract_macro_stmt_mut(&mut self) -> &mut Stmt { - let stmt = self - .function - .block - .stmts - .last_mut() - .expect("There are no statements in your function"); - - if let Stmt::Macro(_) = stmt { - stmt - } else { - panic!("Couldn't extract the rsx! block from the function") - } - } - - pub fn extract_rsx_macro_stmt_mut(&mut self) -> &mut Stmt { - let stmt = self.extract_macro_stmt_mut(); - if let Stmt::Macro(mac) = stmt { - if mac.mac.path.segments.last().unwrap().ident != "rsx" { - panic!("The last statement as a macro is no rsx macro"); - } - } else { - panic!(); - } - stmt - } - - pub fn extract_rsx_macro_mut(&mut self) -> &mut Macro { - if let Stmt::Macro(mac) = self.extract_rsx_macro_stmt_mut() { - &mut mac.mac - } else { - panic!() + fn extract_macro_mut(function: &mut ItemFn) -> syn::Result<&mut Macro> { + let no_stmt_error = syn::Error::new_spanned(&function, "No last statement in function"); + let not_macro_error = syn::Error::new_spanned(&function, "Last statement is no macro"); + let last = function.block.stmts.last_mut().ok_or(no_stmt_error)?; + if let Stmt::Macro(StmtMacro { mac, .. }) = last { + return Ok(mac); } + Err(not_macro_error) } } impl Parse for ElementFunction { fn parse(input: ParseStream) -> syn::Result { - let function: ItemFn = ItemFn::parse(input)?; + let mut function: ItemFn = ItemFn::parse(input)?; let name = function.sig.ident.to_string(); - let element_name = pascal_case(&name); + + let macro_ast = { + let mac = Self::extract_macro_mut(&mut function)?; + parse2(mac.tokens.clone())? + }; + Ok(Self { function, name, - element_name, + macro_ast, }) } } + +impl ToTokens for ElementFunction { + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut function = self.function.clone(); + let mac = Self::extract_macro_mut(&mut function) + .expect("Could not convert ElementFunction to tokens"); + mac.tokens = self.macro_ast.clone().into_token_stream(); + function.to_tokens(tokens); + } +} diff --git a/packages/macros/src/element/handler.rs b/packages/macros/src/element/handler.rs index b8f7e6b..929813c 100644 --- a/packages/macros/src/element/handler.rs +++ b/packages/macros/src/element/handler.rs @@ -4,14 +4,9 @@ // %% includes %% // % extern % -use crate::element::rsx_ast::{Attribute, Attributes, Element}; -use dioxus_rsx::{ - AttributeName, AttributeValue, BodyNode, CallBody, Component, DynIdx, HotLiteral, - HotReloadFormattedSegment, IfmtInput, RsxBlock, RsxItem, Segment, TemplateBody, -}; use proc_macro2::{Span, TokenStream}; use quote::*; -use syn::{parse::Parser, parse2, parse_quote, Expr, Ident, LitStr, Macro, Stmt}; +use syn::{parse2, parse_quote, LitStr}; // % intern % use super::{ @@ -27,6 +22,7 @@ pub struct ElementHandler { impl ElementHandler { pub fn new(attr: TokenStream, item: TokenStream) -> Self { + println!("-----------------------------------------------------------------------------"); Self { attrs: parse2(attr).expect("Could not parse the function attributes"), function: parse2(item).expect("Could not parse the function itself"), @@ -52,7 +48,7 @@ impl ElementHandler { if self.attrs.entry { self.attrs.kind = ElementKind::Entry; self.attrs.no_css = false; - self.handle_css(rsx_main_element); + self.handle_css(); } } @@ -61,7 +57,10 @@ impl ElementHandler { self.handle_as_entries(); self.handle_class(); - let func = &self.function.into_token_stream(); + let func = &self.function; + + println!("{}", func.into_token_stream()); + quote! { #[dioxus::prelude::component] #func @@ -81,34 +80,49 @@ impl ElementHandler { let style_file_lit = LitStr::new(style_file.to_str().unwrap_or_default(), Span::call_site()); - rsx_main_element.elmts.push(parse_quote! { - document::Link { - rel: "stylesheet", - href: asset!(#style_file_lit) - }}); + quote! { + document::Link { + rel: "stylesheet", + href: asset!(#style_file_lit) + } + } + .to_tokens(&mut self.function.macro_ast.body); } fn handle_class(&mut self) { if self.attrs.no_class { return; } + println!("BEFORE ELM"); let elm = { - if let Some(elm) = rsx_main_element + if let Some(elm) = self + .function + .macro_ast .attrs .iter_mut() .find(|attr| attr.name == "class") { elm } else { - rsx_main_element.attrs.push(parse_quote! { class: "", }); - rsx_main_element.attrs.last_mut().unwrap() + self.function + .macro_ast + .attrs + .insert(0, parse_quote! { class: "" }); + // if only the class attribute is present there needs to be trailing puncuation + if self.function.macro_ast.attrs.len() == 1 { + self.function.macro_ast.attrs.push_punct(parse_quote! {,}); + } + self.function.macro_ast.attrs.first_mut().unwrap() } }; - let val = parse2::(elm.value.clone()).expect("Couldn't parse attribute value"); - elm.value = LitStr::new( - &format!("{} Element {}", val.value(), self.function.name), + println!("ELM"); + let previous_value = match elm.value.clone() { + Some(value) => value.value(), + None => elm.name.to_string(), + }; + elm.value = Some(LitStr::new( + &format!("{} Element {}", previous_value, self.function.name), Span::call_site(), - ) - .into_token_stream(); + )); } } diff --git a/packages/macros/src/element/rsx_ast.rs b/packages/macros/src/element/rsx_ast.rs index cb71292..db0a0c8 100644 --- a/packages/macros/src/element/rsx_ast.rs +++ b/packages/macros/src/element/rsx_ast.rs @@ -2,7 +2,7 @@ // %% includes %% use proc_macro2::TokenStream; -use quote::ToTokens; +use quote::{quote, ToTokens}; use syn::{ braced, parse::{Parse, ParseStream}, @@ -12,14 +12,16 @@ use syn::{ Ident, LitStr, Macro, Path, Token, }; +use dioxus_rsx::CallBody; + // %% main %% // % Element % #[derive(Clone)] pub struct Element { pub path: Path, - pub brace: Brace, + pub _brace: Brace, pub attrs: Punctuated, - pub elmts: Punctuated, + pub body: TokenStream, } impl From for Element { @@ -31,37 +33,89 @@ impl From for Element { impl Parse for Element { fn parse(input: ParseStream) -> syn::Result { let content; + let path = input.parse()?; + let _brace = braced!(content in input); + let mut attrs = Punctuated::new(); + while content.peek(Ident) && !(content.peek(Ident) && content.peek2(Brace)) { + let attr = content.parse()?; + attrs.push_value(attr); + if content.peek(Token![,]) { + attrs.push_punct(content.parse()?); + } + } + println!("CONTENT {content}"); + let body = content.parse()?; + println!("SUCCESS"); Ok(Self { - path: input.parse()?, - brace: braced!(content in input), - attrs: content.parse_terminated(Attribute::parse, Token![,])?, - elmts: content.parse_terminated(Element::parse, Token![,])?, + path, + _brace, + attrs, + body, }) } } impl ToTokens for Element { fn to_tokens(&self, tokens: &mut TokenStream) { - todo!() + self.path.to_tokens(tokens); + + let attrs = &self.attrs; + let body = &self.body; + let braced_group = quote! { + { + #attrs + #body + } + }; + tokens.extend(braced_group); } } // % Attribute % #[derive(Clone)] pub struct Attribute { + pub spread: Option, pub name: Ident, - pub colon: Token![:], - pub value: TokenStream, - pub comma: Option, + pub colon: Option, + pub value: Option, } impl Parse for Attribute { fn parse(input: ParseStream) -> syn::Result { - Ok(Self { - name: input.parse()?, - colon: input.parse()?, - value: input.parse()?, - comma: input.parse()?, + println!("INPUT: {input}"); + let spread: Option = if input.peek(Token![..]) { + Some(input.parse()?) + } else { + None + }; + + let name: Ident = input.parse()?; + + let colon: Option = if input.peek(Token![:]) { + Some(input.parse()?) + } else { + None + }; + + let value: Option = if colon.is_some() { + Some(input.parse()?) + } else { + None + }; + + Ok(Attribute { + spread, + name, + colon, + value, }) } } + +impl ToTokens for Attribute { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.name.to_tokens(tokens); + self.colon.to_tokens(tokens); + self.value.to_tokens(tokens); + } +} diff --git a/packages/platforms/desktop/src/main.rs b/packages/platforms/desktop/src/main.rs index c16a52d..8566502 100644 --- a/packages/platforms/desktop/src/main.rs +++ b/packages/platforms/desktop/src/main.rs @@ -1,12 +1,9 @@ -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"); @@ -16,6 +13,6 @@ fn main() { #[component] fn App() -> Element { rsx! { - Router:: {} + Router:: {} } } diff --git a/packages/platforms/desktop/src/router.rs b/packages/platforms/desktop/src/router.rs deleted file mode 100644 index 9112f15..0000000 --- a/packages/platforms/desktop/src/router.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::prelude::{pages::prelude::*, *}; -use simple_ai_macros::entry; - -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/src/main.rs b/packages/platforms/web/src/main.rs index 8576ed6..8566502 100644 --- a/packages/platforms/web/src/main.rs +++ b/packages/platforms/web/src/main.rs @@ -1,18 +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); + dioxus::launch(App); } +#[component] fn App() -> Element { - rsx! { Router:: {} } + rsx! { + Router:: {} + } } diff --git a/packages/platforms/web/src/router.rs b/packages/platforms/web/src/router.rs deleted file mode 100644 index b12d7f4..0000000 --- a/packages/platforms/web/src/router.rs +++ /dev/null @@ -1,37 +0,0 @@ -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 {}, -} From 92fadcd8ad23351aff2c3f0ba5dbdb704684b951 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 12 Oct 2025 23:13:15 +0200 Subject: [PATCH 05/17] work save --- packages/frontend/assets/style/entry.css | 19 ++++++++++++++----- .../assets/style/items/breadcrumbs.css | 2 +- .../frontend/assets/style/pages/start.css | 3 ++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/frontend/assets/style/entry.css b/packages/frontend/assets/style/entry.css index f48137c..7038f5e 100644 --- a/packages/frontend/assets/style/entry.css +++ b/packages/frontend/assets/style/entry.css @@ -23,19 +23,28 @@ } /* %% general %% */ + /* % full size % */ html, body, -main, #main, -svg, -section, -article, -aside { +svg { height: 100%; width: 100%; } +main, +article, +aside, +section { + flex-grow: 1; +} + +#main { + display: flex; + flex-direction: column; +} + /* % no spacing % */ html, body, diff --git a/packages/frontend/assets/style/items/breadcrumbs.css b/packages/frontend/assets/style/items/breadcrumbs.css index c1072f0..e2af46d 100644 --- a/packages/frontend/assets/style/items/breadcrumbs.css +++ b/packages/frontend/assets/style/items/breadcrumbs.css @@ -1,11 +1,11 @@ .Breadcrumbs { - width: 100%; display: flex; flex-direction: row; justify-content: left; align-items: center; padding: 1rem; box-sizing: content-box; + position: sticky; background-color: rgba(var(--color-2), 0.3); } diff --git a/packages/frontend/assets/style/pages/start.css b/packages/frontend/assets/style/pages/start.css index ea4dfcf..1fb3396 100644 --- a/packages/frontend/assets/style/pages/start.css +++ b/packages/frontend/assets/style/pages/start.css @@ -1,8 +1,9 @@ /* %%% pages / start.css %%% */ -main { +.Start { display: flex; align-items: center; justify-content: center; + flex-grow: 1; } div .button-container { From 2dba9d676652d0feed704da84f01313ac02be3cf Mon Sep 17 00:00:00 2001 From: = Date: Tue, 14 Oct 2025 00:09:40 +0200 Subject: [PATCH 06/17] work save --- Cargo.toml | 2 +- .../assets/style/items/breadcrumbs.css | 1 - .../frontend/assets/style/items/top_nav.css | 5 +++++ .../assets/style/items/window_decorations.css | 21 ++++++++++++++++++ packages/frontend/src/modules/components.rs | 4 ++++ .../src/modules/components/breadcrumbs.rs | 2 +- .../src/modules/components/top_nav.rs | 22 +++++++++++++++++++ .../modules/components/window_decorations.rs | 13 +++++++++++ packages/frontend/src/modules/icons.rs | 1 + packages/frontend/src/modules/router.rs | 12 +++------- packages/platforms/desktop/Cargo.toml | 3 +++ packages/platforms/desktop/src/main.rs | 5 ++++- packages/platforms/desktop/src/platform.rs | 4 ++++ .../platforms/desktop/src/platform/linux.rs | 17 ++++++++++++++ 14 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 packages/frontend/assets/style/items/top_nav.css create mode 100644 packages/frontend/assets/style/items/window_decorations.css create mode 100644 packages/frontend/src/modules/components/top_nav.rs create mode 100644 packages/frontend/src/modules/components/window_decorations.rs create mode 100644 packages/platforms/desktop/src/platform.rs create mode 100644 packages/platforms/desktop/src/platform/linux.rs diff --git a/Cargo.toml b/Cargo.toml index 44f051e..312fbc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ simple-ai-backend = { path = "packages/backend" } simple-ai-frontend = { path = "packages/frontend" } simple-ai-macros = { path = "packages/macros" } -dioxus = { version = "0.7.0-rc.0" } +dioxus = { version = "0.7.0-rc.1" } [profile] diff --git a/packages/frontend/assets/style/items/breadcrumbs.css b/packages/frontend/assets/style/items/breadcrumbs.css index e2af46d..3098957 100644 --- a/packages/frontend/assets/style/items/breadcrumbs.css +++ b/packages/frontend/assets/style/items/breadcrumbs.css @@ -6,7 +6,6 @@ padding: 1rem; box-sizing: content-box; position: sticky; - background-color: rgba(var(--color-2), 0.3); } .Breadcrumbs .HomeIcon { diff --git a/packages/frontend/assets/style/items/top_nav.css b/packages/frontend/assets/style/items/top_nav.css new file mode 100644 index 0000000..54bb8ff --- /dev/null +++ b/packages/frontend/assets/style/items/top_nav.css @@ -0,0 +1,5 @@ +.TopNav { + display: flex; + flex-direction: row; + background-color: rgba(var(--color-2), 0.3); +} diff --git a/packages/frontend/assets/style/items/window_decorations.css b/packages/frontend/assets/style/items/window_decorations.css new file mode 100644 index 0000000..8c051d1 --- /dev/null +++ b/packages/frontend/assets/style/items/window_decorations.css @@ -0,0 +1,21 @@ +.WindowDecorations { + display: flex; + flex-direction: row; + align-items: center; +} +.WindowDecorations .Icon { + margin: 0; + padding: 0.2rem; +} +.WindowDecorations section { + flex-grow: 1; + display: flex; + flex-direction: row; + margin: 0 0.5rem; + align-items: center; + cursor: pointer; +} +.WindowDecorations .Icon:hover { + border-radius: 5rem; + background-color: rgba(var(--background-highlight-color), 0.3); +} diff --git a/packages/frontend/src/modules/components.rs b/packages/frontend/src/modules/components.rs index 87c3a4f..e3e839a 100644 --- a/packages/frontend/src/modules/components.rs +++ b/packages/frontend/src/modules/components.rs @@ -16,7 +16,9 @@ pub mod search; pub mod search_result; pub mod section_toggle; pub mod static_param; +pub mod top_nav; pub mod viewport; +pub mod window_decorations; // %% prelude %% pub mod prelude { @@ -32,7 +34,9 @@ pub mod prelude { pub use super::search::*; pub use super::search_result::*; pub use super::section_toggle::*; + pub use super::top_nav::*; pub use super::viewport::*; + pub use super::window_decorations::*; pub mod params { pub use super::super::connection::*; pub use super::super::runtime_param::*; diff --git a/packages/frontend/src/modules/components/breadcrumbs.rs b/packages/frontend/src/modules/components/breadcrumbs.rs index 066b674..e794a82 100644 --- a/packages/frontend/src/modules/components/breadcrumbs.rs +++ b/packages/frontend/src/modules/components/breadcrumbs.rs @@ -12,7 +12,7 @@ pub fn Breadcrumbs() -> Element { crumbs.pop(); let mut assembled = String::new(); rsx! { - div { + main { Link { to: Route::Start {}, HomeIcon {} } for crumb in crumbs.iter() { RightBracketIcon {} diff --git a/packages/frontend/src/modules/components/top_nav.rs b/packages/frontend/src/modules/components/top_nav.rs new file mode 100644 index 0000000..160a797 --- /dev/null +++ b/packages/frontend/src/modules/components/top_nav.rs @@ -0,0 +1,22 @@ +// %%% components / top_nav.rs %%% +// %% includes %% +use super::utils::*; +use super::{breadcrumbs::Breadcrumbs, window_decorations::WindowDecorations}; + +// %% main %% +#[item] +pub fn TopNav() -> Element { + rsx! { + div { + Breadcrumbs {} + WindowDecorations {} + } + } +} + +pub fn TopNavLayout() -> Element { + rsx! { + TopNav {} + Outlet:: {} + } +} diff --git a/packages/frontend/src/modules/components/window_decorations.rs b/packages/frontend/src/modules/components/window_decorations.rs new file mode 100644 index 0000000..eb98b98 --- /dev/null +++ b/packages/frontend/src/modules/components/window_decorations.rs @@ -0,0 +1,13 @@ +// %%% components / window_decorations.rs %%% +// %% includes %% +use super::utils::*; + +// %% main %% +#[item] +pub fn WindowDecorations() -> Element { + rsx! { + div { + section { CloseIcon {} } + } + } +} diff --git a/packages/frontend/src/modules/icons.rs b/packages/frontend/src/modules/icons.rs index 35feb4a..d6e2030 100644 --- a/packages/frontend/src/modules/icons.rs +++ b/packages/frontend/src/modules/icons.rs @@ -5,6 +5,7 @@ use crate::utils::*; // %% main %% icon! { + close: , right_bracket: , home: , projects: , diff --git a/packages/frontend/src/modules/router.rs b/packages/frontend/src/modules/router.rs index 5a98c52..1f7d1d2 100644 --- a/packages/frontend/src/modules/router.rs +++ b/packages/frontend/src/modules/router.rs @@ -1,18 +1,12 @@ pub mod core { + use super::super::components::top_nav::TopNavLayout; use super::super::pages::prelude::*; - use crate::{modules::pages::utils::Breadcrumbs, utils::*}; - - fn TopNav() -> Element { - rsx! { - Breadcrumbs {} - Outlet:: {} - } - } + use crate::utils::*; #[derive(Debug, Clone, Routable, PartialEq)] #[rustfmt::skip] pub enum Route { - #[layout(TopNav)] + #[layout(TopNavLayout)] #[route("/")] Start {}, #[nest("/projects")] diff --git a/packages/platforms/desktop/Cargo.toml b/packages/platforms/desktop/Cargo.toml index c84c194..1bff629 100644 --- a/packages/platforms/desktop/Cargo.toml +++ b/packages/platforms/desktop/Cargo.toml @@ -7,3 +7,6 @@ edition = "2021" dioxus = { workspace = true, features = ["desktop"]} simple-ai-frontend = { workspace = true } simple-ai-macros = { workspace = true } + +[target.'cfg(unix)'.dependencies] +gtk = { version = "0.18.2", package = "gtk" } diff --git a/packages/platforms/desktop/src/main.rs b/packages/platforms/desktop/src/main.rs index 8566502..4d0903b 100644 --- a/packages/platforms/desktop/src/main.rs +++ b/packages/platforms/desktop/src/main.rs @@ -1,4 +1,7 @@ +pub mod platform; + pub mod prelude { + pub(crate) use super::App; pub(crate) use dioxus::prelude::*; pub(crate) use simple_ai_frontend::prelude::*; } @@ -7,7 +10,7 @@ use prelude::*; fn main() { dioxus::logger::init(dioxus::logger::tracing::Level::DEBUG).expect("failed to init logger"); - dioxus::launch(App); + platform::launch(); } #[component] diff --git a/packages/platforms/desktop/src/platform.rs b/packages/platforms/desktop/src/platform.rs new file mode 100644 index 0000000..d2affcf --- /dev/null +++ b/packages/platforms/desktop/src/platform.rs @@ -0,0 +1,4 @@ +#[cfg(unix)] +pub mod linux; +#[cfg(unix)] +pub use linux::launch; diff --git a/packages/platforms/desktop/src/platform/linux.rs b/packages/platforms/desktop/src/platform/linux.rs new file mode 100644 index 0000000..2b52227 --- /dev/null +++ b/packages/platforms/desktop/src/platform/linux.rs @@ -0,0 +1,17 @@ +use crate::prelude::*; +use dioxus::desktop::{muda, tao}; +use muda::Menu; +use tao::window::WindowBuilder; + +pub fn launch() { + let builder = WindowBuilder::new() + .with_title("SimpleAi - (dev)") + .with_decorations(false); + + dioxus::desktop::launch::launch_virtual_dom( + VirtualDom::new(App), + dioxus::desktop::Config::new() + .with_window(builder) + .with_menu(Menu::new()), + ); +} From bfb4d7227e99f7f94bb619064f9d000d65c0e233 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 26 Oct 2025 22:19:23 +0100 Subject: [PATCH 07/17] work save --- packages/frontend/Cargo.toml | 1 + packages/frontend/assets/style/entry.css | 37 +++++------ .../assets/style/items/heading_layout.css | 14 +++++ .../assets/style/items/labeled_box.css | 13 ++++ .../assets/style/items/navigation_page.css | 20 ++++++ .../frontend/assets/style/items/project.css | 36 +++++++++++ .../frontend/assets/style/items/top_nav.css | 2 + .../assets/style/items/top_nav_layout.css | 11 ++++ packages/frontend/assets/style/pages/new.css | 32 ++++++++++ .../frontend/assets/style/pages/projects.css | 1 - .../frontend/assets/style/pages/start.css | 21 ------- packages/frontend/src/modules/components.rs | 6 ++ .../src/modules/components/breadcrumbs.rs | 9 +-- .../src/modules/components/heading_layout.rs | 19 ++++++ .../src/modules/components/labeled_box.rs | 30 +++------ .../src/modules/components/navigation_page.rs | 10 +++ .../src/modules/components/project.rs | 22 +++++++ .../src/modules/components/top_nav.rs | 10 +-- packages/frontend/src/modules/icons.rs | 3 + packages/frontend/src/modules/pages.rs | 2 + packages/frontend/src/modules/pages/editor.rs | 2 +- packages/frontend/src/modules/pages/new.rs | 62 +++---------------- .../frontend/src/modules/pages/project_nav.rs | 19 ++++++ .../frontend/src/modules/pages/projects.rs | 32 ++++++++-- packages/frontend/src/modules/pages/start.rs | 2 +- packages/frontend/src/modules/router.rs | 23 ++++--- 26 files changed, 295 insertions(+), 144 deletions(-) create mode 100644 packages/frontend/assets/style/items/heading_layout.css create mode 100644 packages/frontend/assets/style/items/navigation_page.css create mode 100644 packages/frontend/assets/style/items/project.css create mode 100644 packages/frontend/assets/style/items/top_nav_layout.css create mode 100644 packages/frontend/src/modules/components/heading_layout.rs create mode 100644 packages/frontend/src/modules/components/navigation_page.rs create mode 100644 packages/frontend/src/modules/components/project.rs create mode 100644 packages/frontend/src/modules/pages/project_nav.rs diff --git a/packages/frontend/Cargo.toml b/packages/frontend/Cargo.toml index 2ff1c33..f06db88 100644 --- a/packages/frontend/Cargo.toml +++ b/packages/frontend/Cargo.toml @@ -10,6 +10,7 @@ simple-ai-macros = { workspace = true } async-recursion = { version = "1.1" } futures = "0.3" reqwest = { version = "0.12", features = ["json"] } +change-case = { version = "0.2" } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } toml = { version = "0.9" } diff --git a/packages/frontend/assets/style/entry.css b/packages/frontend/assets/style/entry.css index 7038f5e..c8eb5d3 100644 --- a/packages/frontend/assets/style/entry.css +++ b/packages/frontend/assets/style/entry.css @@ -40,7 +40,8 @@ section { flex-grow: 1; } -#main { +#main, +main { display: flex; flex-direction: column; } @@ -52,24 +53,22 @@ main, section, article, aside, -div { +div, +form, +p { margin: 0; padding: 0; } -/* %% elements %% */ -/* % * % */ * { user-select: none; } -/* % html % */ html { background-color: rgb(var(--background-color)); color: rgb(var(--text-color)); } -/* % Icon % */ .Icon { color: rgb(var(--accent-color)); width: 2rem; @@ -78,16 +77,17 @@ html { padding: 0.5rem; } -/* % button % */ button { padding: 1rem; margin: 1rem; display: flex; justify-content: center; align-items: center; + border-radius: 2rem; + border-style: solid; + cursor: pointer; } -/* % NavbarButton % */ .NavbarButton { color: rgb(var(--accent-color)); display: flex; @@ -110,38 +110,29 @@ a { } /* % h1 % */ -h1 { +h1, +h2, +h3, +h4 { padding: 0; margin: 0; } -/* % form % */ form { align-items: center; display: flex; flex-direction: column; - height: 80%; justify-content: space-around; - width: 90%; -} - -/* % header % */ -header { - width: 100%; - display: flex; - align-items: center; - justify-content: stretch; } -/* % input % */ input { border-width: var(--border-width); background-color: rgba(var(--accent-color), 0.5); border-color: rgb(var(--border-color)); - border-radius: var(--border-radius); + border-radius: 5rem; border-style: none; caret-shape: block; - height: 3rem; + height: 2rem; width: 100%; color: rgb(var(--text-color)); padding: 1rem; diff --git a/packages/frontend/assets/style/items/heading_layout.css b/packages/frontend/assets/style/items/heading_layout.css new file mode 100644 index 0000000..e8dd91b --- /dev/null +++ b/packages/frontend/assets/style/items/heading_layout.css @@ -0,0 +1,14 @@ +.HeadingLayout { + padding: 2rem; + display: flex; + flex-direction: column; +} + +.HeadingLayout > h1 { + padding: 0 0 1rem 0; +} + +.HeadingLayout > article { + display: flex; + flex-direction: column; +} diff --git a/packages/frontend/assets/style/items/labeled_box.css b/packages/frontend/assets/style/items/labeled_box.css index e69de29..9f12cea 100644 --- a/packages/frontend/assets/style/items/labeled_box.css +++ b/packages/frontend/assets/style/items/labeled_box.css @@ -0,0 +1,13 @@ +.LabeledBox { + display: flex; + flex-direction: row; + align-items: center; +} + +.LabeledBox > h5 { + padding: 1rem; +} + +.LabeledBox > input { + flex-grow: 1; +} diff --git a/packages/frontend/assets/style/items/navigation_page.css b/packages/frontend/assets/style/items/navigation_page.css new file mode 100644 index 0000000..86ea9b9 --- /dev/null +++ b/packages/frontend/assets/style/items/navigation_page.css @@ -0,0 +1,20 @@ +.NavigationPage { + display: flex; + align-items: center; + justify-content: center; + flex-grow: 1; +} + +.NavigationPage .button-container { + width: 30rem; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-around; +} + +.NavButton .Icon { + border-radius: 4rem; + border-style: solid; + border-width: 0.2rem; +} diff --git a/packages/frontend/assets/style/items/project.css b/packages/frontend/assets/style/items/project.css new file mode 100644 index 0000000..a8af8ca --- /dev/null +++ b/packages/frontend/assets/style/items/project.css @@ -0,0 +1,36 @@ +.Project { + padding: 1rem; + margin: 1rem 0; + display: flex; + flex-direction: row; + border-style: solid; + border-width: 0.15rem; + border-radius: 1rem; +} + +.Project .infos { + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.Project .actions { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.Project .actions .Icon { + flex-grow: 1; +} + +.Project > h3 { +} + +.Project > .date { + font-size: 0.8rem; + font-style: italic; +} + +.Project > .desc { +} diff --git a/packages/frontend/assets/style/items/top_nav.css b/packages/frontend/assets/style/items/top_nav.css index 54bb8ff..838761b 100644 --- a/packages/frontend/assets/style/items/top_nav.css +++ b/packages/frontend/assets/style/items/top_nav.css @@ -1,5 +1,7 @@ .TopNav { + width: 100%; display: flex; flex-direction: row; background-color: rgba(var(--color-2), 0.3); + z-index: 100; } diff --git a/packages/frontend/assets/style/items/top_nav_layout.css b/packages/frontend/assets/style/items/top_nav_layout.css new file mode 100644 index 0000000..f101076 --- /dev/null +++ b/packages/frontend/assets/style/items/top_nav_layout.css @@ -0,0 +1,11 @@ +.TopNavLayout { + overflow: hidden; + display: flex; + flex-direction: column; + flex-grow: 1; +} + +.TopNavLayout > article { + overflow: scroll; + display: flex; +} diff --git a/packages/frontend/assets/style/pages/new.css b/packages/frontend/assets/style/pages/new.css index e69de29..4678a9a 100644 --- a/packages/frontend/assets/style/pages/new.css +++ b/packages/frontend/assets/style/pages/new.css @@ -0,0 +1,32 @@ +.New { + display: flex; +} + +.New > form { + flex-grow: 1; + justify-content: space-between; +} + +.New .LabeledBox { + width: 100%; +} + +.New button { + height: 8rem; + width: 8rem; + border-radius: 5rem; + background-color: rgb(var(--background-highlight-color)); + border-style: none; + color: rgb(var(--text-color)); +} + +.New button .Icon { + scale: 1.2; + color: rgb(var(--text-color)); +} + +.New .button-wrapper { + display: flex; + justify-content: center; + align-items: center; +} diff --git a/packages/frontend/assets/style/pages/projects.css b/packages/frontend/assets/style/pages/projects.css index 99d45ac..f4b116e 100644 --- a/packages/frontend/assets/style/pages/projects.css +++ b/packages/frontend/assets/style/pages/projects.css @@ -1,5 +1,4 @@ .Projects { - padding: 2rem; display: flex; flex-direction: column; gap: 1rem; diff --git a/packages/frontend/assets/style/pages/start.css b/packages/frontend/assets/style/pages/start.css index 1fb3396..e69de29 100644 --- a/packages/frontend/assets/style/pages/start.css +++ b/packages/frontend/assets/style/pages/start.css @@ -1,21 +0,0 @@ -/* %%% pages / start.css %%% */ -.Start { - display: flex; - align-items: center; - justify-content: center; - flex-grow: 1; -} - -div .button-container { - width: 30rem; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-around; -} - -.NavButton .Icon { - border-radius: 4rem; - border-style: solid; - border-width: 0.2rem; -} diff --git a/packages/frontend/src/modules/components.rs b/packages/frontend/src/modules/components.rs index e3e839a..f6c6d6d 100644 --- a/packages/frontend/src/modules/components.rs +++ b/packages/frontend/src/modules/components.rs @@ -8,9 +8,12 @@ pub mod drag_area; pub mod draggable; pub mod focus_button; pub mod focus_button_array; +pub mod heading_layout; pub mod labeled_box; pub mod nav_button; +pub mod navigation_page; pub mod node; +pub mod project; pub mod runtime_param; pub mod search; pub mod search_result; @@ -28,9 +31,12 @@ pub mod prelude { pub use super::draggable::*; pub use super::focus_button::*; pub use super::focus_button_array::*; + pub use super::heading_layout::*; pub use super::labeled_box::*; pub use super::nav_button::*; + pub use super::navigation_page::*; pub use super::node::*; + pub use super::project; pub use super::search::*; pub use super::search_result::*; pub use super::section_toggle::*; diff --git a/packages/frontend/src/modules/components/breadcrumbs.rs b/packages/frontend/src/modules/components/breadcrumbs.rs index e794a82..5055f8a 100644 --- a/packages/frontend/src/modules/components/breadcrumbs.rs +++ b/packages/frontend/src/modules/components/breadcrumbs.rs @@ -1,15 +1,10 @@ -// %%% components / breadcrumbs.rs %%% -/// The top navigation which shows you where you are. -// -// %% includes %% use super::utils::*; -// %% main %% #[item] pub fn Breadcrumbs() -> Element { let route_str = router().full_route_string(); - let mut crumbs: Vec<&str> = route_str.rsplit_terminator("/").collect(); - crumbs.pop(); + let mut crumbs: Vec<&str> = route_str.split_terminator("/").collect(); + crumbs.remove(0); let mut assembled = String::new(); rsx! { main { diff --git a/packages/frontend/src/modules/components/heading_layout.rs b/packages/frontend/src/modules/components/heading_layout.rs new file mode 100644 index 0000000..52cf725 --- /dev/null +++ b/packages/frontend/src/modules/components/heading_layout.rs @@ -0,0 +1,19 @@ +use super::utils::*; +use change_case::pascal_case; + +#[item] +pub fn HeadingLayout() -> Element { + let route_str = router().full_route_string(); + let mut route_elms: Vec<&str> = route_str.split_terminator("/").collect(); + let unfmt_name = route_elms.last().expect(&format!( + "Heading Layout errored because of unsatisfied route elements: {:?}", + route_elms, + )); + let name = pascal_case(unfmt_name); + rsx! { + main { + h1 { {name} } + article { Outlet:: {} } + } + } +} diff --git a/packages/frontend/src/modules/components/labeled_box.rs b/packages/frontend/src/modules/components/labeled_box.rs index 0fad754..95987dc 100644 --- a/packages/frontend/src/modules/components/labeled_box.rs +++ b/packages/frontend/src/modules/components/labeled_box.rs @@ -1,26 +1,16 @@ -// %%% components / labeled_box.rs %%% - -// %% includes %% use super::utils::*; -// %% main %% -#[component] -pub fn LabeledBox(children: Element) -> Element { - let script = r#####" -((c) =>{ - console.log("hello"); - let l = c.parentElement; - console.log(l); - let middleIndex = Math.floor(l.children.length / 2); - let div = document.createElement("div"); - div.className = "divider"; - l.insertBefore(div, l.children[middleIndex + 1]); -})(document.currentScript); -"#####; +#[item] +pub fn LabeledBox(name: String, kind: String, required: bool, placeholder: String) -> Element { rsx! { - div { class: "LabeledBox", - script { {script} } - {children} + div { + h5 { {name.clone()} } + input { + name, + required, + placeholder, + r#type: kind, + } } } } diff --git a/packages/frontend/src/modules/components/navigation_page.rs b/packages/frontend/src/modules/components/navigation_page.rs new file mode 100644 index 0000000..a48d85b --- /dev/null +++ b/packages/frontend/src/modules/components/navigation_page.rs @@ -0,0 +1,10 @@ +use super::utils::*; + +#[item] +pub fn NavigationPage(children: Element) -> Element { + rsx! { + main { + div { class: "button-container", {children} } + } + } +} diff --git a/packages/frontend/src/modules/components/project.rs b/packages/frontend/src/modules/components/project.rs new file mode 100644 index 0000000..692a045 --- /dev/null +++ b/packages/frontend/src/modules/components/project.rs @@ -0,0 +1,22 @@ +use super::utils::*; +use super::{breadcrumbs::Breadcrumbs, window_decorations::WindowDecorations}; +use chrono::{DateTime, Utc}; + +#[item] +pub fn Project(name: String, date: DateTime, desc: String) -> Element { + rsx! { + div { + section { class: "infos", + h3 { class: "name", {name} } + p { class: "date", {date.naive_local().date().to_string()} } + br { class: "spacer" } + p { class: "desc", {desc} } + } + section { class: "actions", + Link { to: Route::ProjectNav {}, class: "open", FolderOpenIcon {} } + Link { to: Route::Editor {}, class: "edit", SettingsIcon {} } + Link { to: Route::Editor {}, class: "delete", TrashIcon {} } + } + } + } +} diff --git a/packages/frontend/src/modules/components/top_nav.rs b/packages/frontend/src/modules/components/top_nav.rs index 160a797..0715e13 100644 --- a/packages/frontend/src/modules/components/top_nav.rs +++ b/packages/frontend/src/modules/components/top_nav.rs @@ -1,9 +1,6 @@ -// %%% components / top_nav.rs %%% -// %% includes %% use super::utils::*; use super::{breadcrumbs::Breadcrumbs, window_decorations::WindowDecorations}; -// %% main %% #[item] pub fn TopNav() -> Element { rsx! { @@ -14,9 +11,12 @@ pub fn TopNav() -> Element { } } +#[item] pub fn TopNavLayout() -> Element { rsx! { - TopNav {} - Outlet:: {} + div { + TopNav {} + article { Outlet:: {} } + } } } diff --git a/packages/frontend/src/modules/icons.rs b/packages/frontend/src/modules/icons.rs index d6e2030..63e42c8 100644 --- a/packages/frontend/src/modules/icons.rs +++ b/packages/frontend/src/modules/icons.rs @@ -5,6 +5,9 @@ use crate::utils::*; // %% main %% icon! { + settings: , + folder_open: , + trash: , close: , right_bracket: , home: , diff --git a/packages/frontend/src/modules/pages.rs b/packages/frontend/src/modules/pages.rs index 4244c2d..4f433c9 100644 --- a/packages/frontend/src/modules/pages.rs +++ b/packages/frontend/src/modules/pages.rs @@ -3,6 +3,7 @@ // %% exports %% pub mod editor; pub mod new; +pub mod project_nav; pub mod projects; pub mod search; pub mod start; @@ -11,6 +12,7 @@ pub mod start; pub mod prelude { pub use super::editor::Editor; pub use super::new::New; + pub use super::project_nav::ProjectNav; pub use super::projects::Projects; 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 index 65b059b..af9c0a6 100644 --- a/packages/frontend/src/modules/pages/editor.rs +++ b/packages/frontend/src/modules/pages/editor.rs @@ -5,7 +5,7 @@ use super::utils::*; use std::collections::HashMap; // %% main %% -#[component] +#[page] pub fn Editor() -> Element { let mut section_contents_map = use_signal(HashMap::new); diff --git a/packages/frontend/src/modules/pages/new.rs b/packages/frontend/src/modules/pages/new.rs index 810679b..22198f8 100644 --- a/packages/frontend/src/modules/pages/new.rs +++ b/packages/frontend/src/modules/pages/new.rs @@ -1,67 +1,25 @@ -// %%% pages / new.rs %%% - -// %% includes %% use super::utils::*; -// %% main %% #[page] pub fn New() -> Element { rsx! { main { form { LabeledBox { - label { r#for: "name", "node name" } - input { - id: "name", - name: "name", - r#type: "text", - required: "true", - placeholder: "SampleProject", - } - } - LabeledBox { - input { - id: "name", - name: "name", - r#type: "text", - required: "true", - placeholder: "SampleProject", - } - label { r#for: "name", "project name" } - } - input { r#type: "text" } - LabeledBox { - label { r#for: "name", "project name" } - input { - id: "name", - name: "name", - r#type: "text", - required: "true", - placeholder: "SampleProject", - } + name: "name", + kind: "text", + required: true, + placeholder: "SampleProject", } LabeledBox { - input { - id: "name", - name: "name", - r#type: "text", - required: "true", - placeholder: "SampleProject", - } - label { r#for: "name", "project name" } + name: "description", + kind: "text", + required: false, + placeholder: "this is a description", } - input { r#type: "file" } - input { r#type: "range" } - - input { r#type: "list", list: "options" } - - datalist { id: "options", - option { value: "1", "1" } - option { value: "2", "2" } - option { value: "3", "3" } + section { class: "button-wrapper", + button { r#type: "submit", NewIcon {} } } - - button { r#type: "submit", "create" } } } } diff --git a/packages/frontend/src/modules/pages/project_nav.rs b/packages/frontend/src/modules/pages/project_nav.rs new file mode 100644 index 0000000..83f2132 --- /dev/null +++ b/packages/frontend/src/modules/pages/project_nav.rs @@ -0,0 +1,19 @@ +use super::utils::*; + +#[page(no_css = true)] +pub fn ProjectNav() -> Element { + rsx! { + main { + NavigationPage { + NavButton { to: Route::Projects {}, + ProjectsIcon {} + p { "onnx-editor" } + } + NavButton { to: Route::Projects {}, + ProjectsIcon {} + p { "export" } + } + } + } + } +} diff --git a/packages/frontend/src/modules/pages/projects.rs b/packages/frontend/src/modules/pages/projects.rs index f679566..c082859 100644 --- a/packages/frontend/src/modules/pages/projects.rs +++ b/packages/frontend/src/modules/pages/projects.rs @@ -1,16 +1,36 @@ -// %%% pages / start.rs %%% - -// %% includes %% +use super::super::components::project::Project; use super::utils::*; +use chrono::{DateTime, Utc}; use dioxus::router::NavigationTarget; -// %% main %% #[page] pub fn Projects() -> Element { rsx! { main { - h1 { class: "projects-heading", "Projects" } - div { class: "projects-view" } + article { class: "projects-wrapper", + div { class: "projects-view", + Project { + name: "sample project", + date: Utc::now(), + desc: "this is a sample Project for development", + } + Project { + name: "sample project", + date: Utc::now(), + desc: "this is a sample Project for development", + } + Project { + name: "sample project", + date: Utc::now(), + desc: "this is a sample Project for development", + } + Project { + name: "sample project", + date: Utc::now(), + desc: "this is a sample Project for development", + } + } + } } } } diff --git a/packages/frontend/src/modules/pages/start.rs b/packages/frontend/src/modules/pages/start.rs index a454dbc..222456e 100644 --- a/packages/frontend/src/modules/pages/start.rs +++ b/packages/frontend/src/modules/pages/start.rs @@ -8,7 +8,7 @@ use super::utils::*; pub fn Start() -> Element { rsx! { main { - div { class: "button-container", + NavigationPage { NavButton { class: "editor", to: Route::Projects {}, ProjectsIcon {} p { "projects" } diff --git a/packages/frontend/src/modules/router.rs b/packages/frontend/src/modules/router.rs index 1f7d1d2..a3eb764 100644 --- a/packages/frontend/src/modules/router.rs +++ b/packages/frontend/src/modules/router.rs @@ -1,6 +1,8 @@ pub mod core { - use super::super::components::top_nav::TopNavLayout; - use super::super::pages::prelude::*; + use super::super::{ + components::{heading_layout::HeadingLayout, top_nav::TopNavLayout}, + pages::prelude::*, + }; use crate::utils::*; #[derive(Debug, Clone, Routable, PartialEq)] @@ -9,13 +11,20 @@ pub mod core { #[layout(TopNavLayout)] #[route("/")] Start {}, - #[nest("/projects")] + #[nest("/editor")] #[route("/")] - Projects {}, + Editor {}, #[end_nest] - #[nest("/new")] - #[route("/")] - New {}, + #[layout(HeadingLayout)] + #[nest("/projects")] + #[route("/")] + Projects {}, + #[route("/projects_nav")] + ProjectNav {}, + #[end_nest] + #[nest("/new")] + #[route("/")] + New {}, } } From 4b2687ec6c2aadfc0725d7ca1b93422f377896b5 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 22 Nov 2025 16:43:02 +0100 Subject: [PATCH 08/17] work save --- packages/frontend/assets/style/entry.css | 107 ++++++++++++++++-- .../assets/style/items/navigation_page.css | 9 +- .../frontend/assets/style/items/project.css | 72 ++++++++++-- packages/frontend/assets/style/pages/new.css | 5 - .../src/modules/components/project.rs | 1 + packages/frontend/src/modules/pages/new.rs | 45 +++++--- packages/macros/Cargo.toml | 1 + packages/macros/src/formifiable.rs | 42 +++++++ packages/macros/src/lib.rs | 13 +-- 9 files changed, 243 insertions(+), 52 deletions(-) create mode 100644 packages/macros/src/formifiable.rs diff --git a/packages/frontend/assets/style/entry.css b/packages/frontend/assets/style/entry.css index c8eb5d3..f04bc3c 100644 --- a/packages/frontend/assets/style/entry.css +++ b/packages/frontend/assets/style/entry.css @@ -9,7 +9,77 @@ --color-4: 206, 214, 232; --color-5: 250, 250, 250; - /* % colors % */ + --color-primary-50: #f7f8fc; + --color-primary-100: #ecedf8; + --color-primary-200: #d9daf2; + --color-primary-300: #bbbde8; + --color-primary-400: #767bd0; + --color-primary-500: #3c42af; + --color-primary-600: #2b2f7d; + --color-primary-700: #1f225b; + --color-primary-800: #121435; + --color-primary-900: #090a1b; + --color-primary-950: #05060f; + --color-secondary-50: #f8f7fc; + --color-secondary-100: #eeecf8; + --color-secondary-200: #ddd9f2; + --color-secondary-300: #c1bbe8; + --color-secondary-400: #8476d0; + --color-secondary-500: #4d3caf; + --color-secondary-600: #372b7d; + --color-secondary-700: #281f5b; + --color-secondary-800: #171235; + --color-secondary-900: #0c091b; + --color-secondary-950: #07050f; + --color-tertiary-50: #f9f7fc; + --color-tertiary-100: #f0ecf8; + --color-tertiary-200: #e2d9f2; + --color-tertiary-300: #cabbe8; + --color-tertiary-400: #9676d0; + --color-tertiary-500: #643caf; + --color-tertiary-600: #482b7d; + --color-tertiary-700: #341f5b; + --color-tertiary-800: #1e1235; + --color-tertiary-900: #0f091b; + --color-tertiary-950: #09050f; + --color-quaternary-50: #fcf7fc; + --color-quaternary-100: #f8ecf7; + --color-quaternary-200: #f2d9ee; + --color-quaternary-300: #e8bbe1; + --color-quaternary-400: #d076c3; + --color-quaternary-500: #af3c9e; + --color-quaternary-600: #7d2b71; + --color-quaternary-700: #5b1f52; + --color-quaternary-800: #351230; + --color-quaternary-900: #1b0918; + --color-quaternary-950: #0f050e; + --color-quinary-50: #fafcf7; + --color-quinary-100: #f2f8ec; + --color-quinary-200: #e4f2d9; + --color-quinary-300: #cfe8bb; + --color-quinary-400: #9fd076; + --color-quinary-500: #70af3c; + --color-quinary-600: #507d2b; + --color-quinary-700: #3a5b1f; + --color-quinary-800: #223512; + --color-quinary-900: #111b09; + --color-quinary-950: #0a0f05; + + /* color with names */ + --color-bg: var(--color-primary-950); + --color-text-fg: var(--color-primary-50); + --color-text-fg-ll: var(--color-primary-400); + --color-icon-bg: var(--color-primary-700); + --color-box-bg: var(--color-primary-700); + --color-border-ll: var(--color-primary-800); + --color-delete-fg: var(--color-quaternary-400); + --color-delete-bg: var(--color-quaternary-500); + --color-edit-fg: var(--color-primary-400); + --color-edit-bg: var(--color-primary-400); + --color-ok-fg: var(--color-quinary-400); + --color-ok-bg: var(--color-quinary-500); + + /* % colors legacy % */ --background-color: var(--color-1); --main-color: var(--color-4); --text-color: var(--color-5); @@ -28,7 +98,8 @@ html, body, #main, -svg { +svg, +section { height: 100%; width: 100%; } @@ -81,10 +152,10 @@ button { padding: 1rem; margin: 1rem; display: flex; + background-color: var(--color-primary-700); justify-content: center; align-items: center; border-radius: 2rem; - border-style: solid; cursor: pointer; } @@ -122,21 +193,21 @@ form { align-items: center; display: flex; flex-direction: column; - justify-content: space-around; + justify-content: start; + gap: 2rem; + padding: 2rem; } input { - border-width: var(--border-width); - background-color: rgba(var(--accent-color), 0.5); - border-color: rgb(var(--border-color)); + background-color: var(); border-radius: 5rem; border-style: none; caret-shape: block; - height: 2rem; + height: 3rem; width: 100%; - color: rgb(var(--text-color)); + box-sizing: border-box; + color: var(--color-primary-50); padding: 1rem; - margin: 1rem; } input:focus { @@ -191,3 +262,19 @@ input[type="range"]::-webkit-slider-runnable-track { input[type="range"]::-webkit-slider-thumb:focus { background-color: rgb(var(--border-color)); } + +.FormifyForm { +} + +.FormifyBox { + display: flex; + align-items: center; + gap: 1rem; + padding: 0.5rem; +} + +.FormifyText { +} + +.FormifyInput { +} diff --git a/packages/frontend/assets/style/items/navigation_page.css b/packages/frontend/assets/style/items/navigation_page.css index 86ea9b9..1dab3c3 100644 --- a/packages/frontend/assets/style/items/navigation_page.css +++ b/packages/frontend/assets/style/items/navigation_page.css @@ -15,6 +15,11 @@ .NavButton .Icon { border-radius: 4rem; - border-style: solid; - border-width: 0.2rem; + padding: 0.8rem; + margin: 1rem; + background-color: var(--color-primary-300); +} + +.NavButton .Icon > svg { + color: var(--color-bg); } diff --git a/packages/frontend/assets/style/items/project.css b/packages/frontend/assets/style/items/project.css index a8af8ca..efecf58 100644 --- a/packages/frontend/assets/style/items/project.css +++ b/packages/frontend/assets/style/items/project.css @@ -1,36 +1,86 @@ .Project { - padding: 1rem; + --project-padding: 1rem; + --project-border-radius: 1rem; + --project-items-border-radius: calc( + var(--project-border-radius) - var(--project-padding) / 2 + ); + + padding: var(--project-padding); margin: 1rem 0; display: flex; flex-direction: row; - border-style: solid; - border-width: 0.15rem; - border-radius: 1rem; + background-color: var(--color-box-bg); + border-radius: var(--project-border-radius); + gap: 1rem; +} + +.Project .divider { + background-color: var(--color-text-fg-ll); + width: 0.1rem; } .Project .infos { + box-sizing: border-box; + position: relative; display: flex; flex-direction: column; justify-content: space-between; } +.Project .infos .date { + font-size: 0.8rem; + position: absolute; + right: 0; +} .Project .actions { display: flex; flex-direction: row; - justify-content: space-between; + justify-content: space-evenly; + align-items: center; + gap: 1rem; } - .Project .actions .Icon { flex-grow: 1; + color: var(--color-text-fg); + margin: 0; } -.Project > h3 { +.Project .actions a { + height: 100%; + flex-grow: 1; + border-radius: var(--project-items-border-radius); + display: flex; + align-items: center; + box-sizing: border-box; } -.Project > .date { - font-size: 0.8rem; - font-style: italic; +.Project .actions a { + opacity: 0.7; + scale: 1; + transition: + opacity 0.3s ease-in-out, + scale 0.1s ease-in-out; +} +.Project .actions a:hover { + opacity: 1; + scale: 1.1; } -.Project > .desc { +.Project .actions .open { + background-color: var(--color-ok-bg); +} +.Project .actions .open:hover { + background-color: var(--color-ok-bg); +} +.Project .actions .edit { + background-color: var(--color-edit-bg); +} +.Project .actions .edit:hover { + background-color: var(--color-edit-bg); +} +.Project .actions .delete { + background-color: var(--color-delete-bg); +} +.Project .actions .delete:hover { + background-color: var(--color-delete-bg); } diff --git a/packages/frontend/assets/style/pages/new.css b/packages/frontend/assets/style/pages/new.css index 4678a9a..fa34246 100644 --- a/packages/frontend/assets/style/pages/new.css +++ b/packages/frontend/assets/style/pages/new.css @@ -2,11 +2,6 @@ display: flex; } -.New > form { - flex-grow: 1; - justify-content: space-between; -} - .New .LabeledBox { width: 100%; } diff --git a/packages/frontend/src/modules/components/project.rs b/packages/frontend/src/modules/components/project.rs index 692a045..19a599d 100644 --- a/packages/frontend/src/modules/components/project.rs +++ b/packages/frontend/src/modules/components/project.rs @@ -12,6 +12,7 @@ pub fn Project(name: String, date: DateTime, desc: String) -> Element { br { class: "spacer" } p { class: "desc", {desc} } } + div { class: "divider" } section { class: "actions", Link { to: Route::ProjectNav {}, class: "open", FolderOpenIcon {} } Link { to: Route::Editor {}, class: "edit", SettingsIcon {} } diff --git a/packages/frontend/src/modules/pages/new.rs b/packages/frontend/src/modules/pages/new.rs index 22198f8..fe54641 100644 --- a/packages/frontend/src/modules/pages/new.rs +++ b/packages/frontend/src/modules/pages/new.rs @@ -1,26 +1,37 @@ use super::utils::*; +#[derive(Formifiable)] +pub struct Project { + pub name: String, + pub description: String, +} + #[page] pub fn New() -> Element { + let mut proj = Project { + name: "".into(), + description: "".into(), + }; rsx! { main { - form { - LabeledBox { - name: "name", - kind: "text", - required: true, - placeholder: "SampleProject", - } - LabeledBox { - name: "description", - kind: "text", - required: false, - placeholder: "this is a description", - } - section { class: "button-wrapper", - button { r#type: "submit", NewIcon {} } - } - } + {proj.rsx_form()} + // form { + // LabeledBox { + // name: "name", + // kind: "text", + // required: true, + // placeholder: "SampleProject", + // } + // LabeledBox { + // name: "description", + // kind: "text", + // required: false, + // placeholder: "this is a description", + // } + // section { class: "button-wrapper", + // button { r#type: "submit", NewIcon {} } + // } + // } } } } diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml index e0667f9..49d3a16 100644 --- a/packages/macros/Cargo.toml +++ b/packages/macros/Cargo.toml @@ -16,6 +16,7 @@ dioxus-autofmt = { version = "0.6" } rstml = { version = "0.12" } toml = { version = "0.9" } serde = { version = "1.0" } +dioxus = { version = "0.7.1" } dioxus-rsx = { version = "0.7.0-rc.0"} [dev-dependencies] diff --git a/packages/macros/src/formifiable.rs b/packages/macros/src/formifiable.rs new file mode 100644 index 0000000..213f8dd --- /dev/null +++ b/packages/macros/src/formifiable.rs @@ -0,0 +1,42 @@ +use proc_macro2::{Span, TokenStream}; +use quote::*; +use syn::{parse2, ItemStruct, LitStr}; + +pub fn macro_impl(item: TokenStream) -> TokenStream { + let struct_item: ItemStruct = parse2(item).expect("No valid struct!"); + let struct_ident = struct_item.ident; + let mut inputs = TokenStream::new(); + for field in struct_item.fields { + let field_ident = field.ident.unwrap(); + let field_name = LitStr::new(&field_ident.to_string(), Span::call_site()); + quote! { + section { + class: "FormifyBox", + h1 { + class: "FormifyText", + #field_name + } + input { + class: "FormifyInput", + name: {#field_name}, + required: true, + r#type: "text", + } + } + } + .to_tokens(&mut inputs); + } + quote! { + use dioxus::prelude::*; + impl #struct_ident { + pub fn rsx_form(&mut self) -> Element { + rsx! { + form { + class: "FormifyForm", + #inputs + } + } + } + } + } +} diff --git a/packages/macros/src/lib.rs b/packages/macros/src/lib.rs index 8881deb..0b1f1d3 100644 --- a/packages/macros/src/lib.rs +++ b/packages/macros/src/lib.rs @@ -1,15 +1,11 @@ -// %%% SimpleAI macros lib.rs %%% -// %% tests %% mod tests; -// %% icon %% mod icon; #[proc_macro] pub fn icon(item: proc_macro::TokenStream) -> proc_macro::TokenStream { icon::macro_impl(syn::parse_macro_input!(item)).into() } -// %% element %% mod element; #[proc_macro_attribute] pub fn element( @@ -19,7 +15,6 @@ pub fn element( element::macro_impl(syn::parse_macro_input!(attr), syn::parse_macro_input!(item)).into() } -// %% component %% mod item; #[proc_macro_attribute] pub fn item( @@ -29,7 +24,6 @@ pub fn item( item::macro_impl(syn::parse_macro_input!(attr), syn::parse_macro_input!(item)).into() } -// %% page %% mod page; #[proc_macro_attribute] pub fn page( @@ -39,7 +33,6 @@ pub fn page( page::macro_impl(syn::parse_macro_input!(attr), syn::parse_macro_input!(item)).into() } -// %% entry %% mod entry; #[proc_macro_attribute] pub fn entry( @@ -48,3 +41,9 @@ pub fn entry( ) -> proc_macro::TokenStream { entry::macro_impl(syn::parse_macro_input!(attr), syn::parse_macro_input!(item)).into() } + +mod formifiable; +#[proc_macro_derive(Formifiable)] +pub fn formifiable(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + formifiable::macro_impl(syn::parse_macro_input!(item)).into() +} From 1255b5ae1a2db8ac6de3d2a003063fe9e8f1376d Mon Sep 17 00:00:00 2001 From: = Date: Sun, 23 Nov 2025 21:21:40 +0100 Subject: [PATCH 09/17] navigations links and css --- Cargo.toml | 2 +- packages/frontend/assets/style/entry.css | 156 ++++-------- .../assets/style/items/navigation_link.css | 13 + .../frontend/assets/style/items/project.css | 18 +- .../frontend/assets/style/items/top_nav.css | 2 +- .../icons/con1/components/search/sample.svg | 0 .../con1/components/searchresult/sample.svg | 0 .../assets/themes/icons/con1/index.theme | 0 .../themes/icons/con1/pages/editor/sample.svg | 0 .../themes/icons/con1/pages/new/sample.svg | 0 .../themes/icons/con1/pages/search/sample.svg | 0 .../themes/icons/con1/pages/start/editor.svg | 1 - .../themes/icons/con1/pages/start/new.svg | 1 - .../themes/icons/con1/pages/start/search.svg | 1 - .../assets/themes/styles/cybr1/colors.css | 44 ---- .../cybr1/components/connection/config.css | 7 - .../cybr1/components/connection/index.css | 13 - .../cybr1/components/divider/config.css | 0 .../styles/cybr1/components/divider/index.css | 22 -- .../styles/cybr1/components/node/config.css | 9 - .../styles/cybr1/components/node/index.css | 33 --- .../cybr1/components/runtimeparam/config.css | 7 - .../cybr1/components/runtimeparam/index.css | 9 - .../styles/cybr1/components/search/config.css | 0 .../styles/cybr1/components/search/index.css | 83 ------- .../cybr1/components/searchresult/config.css | 0 .../cybr1/components/searchresult/index.css | 107 -------- .../cybr1/components/staticparam/config.css | 0 .../cybr1/components/staticparam/index.css | 0 .../assets/themes/styles/cybr1/config.css | 19 -- .../assets/themes/styles/cybr1/icon.ico | 0 .../assets/themes/styles/cybr1/index.css | 230 ------------------ .../assets/themes/styles/cybr1/index.theme | 0 .../styles/cybr1/pages/editor/config.css | 0 .../styles/cybr1/pages/editor/index.css | 36 --- .../themes/styles/cybr1/pages/new/config.css | 0 .../themes/styles/cybr1/pages/new/index.css | 14 -- .../styles/cybr1/pages/search/config.css | 0 .../styles/cybr1/pages/search/index.css | 3 - .../styles/cybr1/pages/start/config.css | 0 .../themes/styles/cybr1/pages/start/index.css | 8 - packages/frontend/src/modules/components.rs | 2 + .../src/modules/components/nav_button.rs | 5 - .../src/modules/components/navigation_link.rs | 19 ++ .../src/modules/components/top_nav.rs | 15 +- .../modules/components/window_decorations.rs | 24 +- packages/frontend/src/modules/icons.rs | 4 - packages/frontend/src/modules/pages/start.rs | 10 +- packages/macros/src/element/handler.rs | 8 - packages/macros/src/element/rsx_ast.rs | 3 +- packages/macros/src/entry.rs | 9 - packages/macros/src/icon.rs | 24 +- 52 files changed, 147 insertions(+), 814 deletions(-) create mode 100644 packages/frontend/assets/style/items/navigation_link.css delete mode 100644 packages/frontend/assets/themes/icons/con1/components/search/sample.svg delete mode 100644 packages/frontend/assets/themes/icons/con1/components/searchresult/sample.svg delete mode 100644 packages/frontend/assets/themes/icons/con1/index.theme delete mode 100644 packages/frontend/assets/themes/icons/con1/pages/editor/sample.svg delete mode 100644 packages/frontend/assets/themes/icons/con1/pages/new/sample.svg delete mode 100644 packages/frontend/assets/themes/icons/con1/pages/search/sample.svg delete mode 100644 packages/frontend/assets/themes/icons/con1/pages/start/editor.svg delete mode 100644 packages/frontend/assets/themes/icons/con1/pages/start/new.svg delete mode 100644 packages/frontend/assets/themes/icons/con1/pages/start/search.svg delete mode 100644 packages/frontend/assets/themes/styles/cybr1/colors.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/connection/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/connection/index.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/divider/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/divider/index.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/node/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/node/index.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/index.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/search/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/search/index.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/searchresult/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/searchresult/index.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/staticparam/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/components/staticparam/index.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/icon.ico delete mode 100644 packages/frontend/assets/themes/styles/cybr1/index.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/index.theme delete mode 100644 packages/frontend/assets/themes/styles/cybr1/pages/editor/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/pages/editor/index.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/pages/new/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/pages/new/index.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/pages/search/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/pages/search/index.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/pages/start/config.css delete mode 100644 packages/frontend/assets/themes/styles/cybr1/pages/start/index.css create mode 100644 packages/frontend/src/modules/components/navigation_link.rs diff --git a/Cargo.toml b/Cargo.toml index 312fbc6..cadce60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ simple-ai-backend = { path = "packages/backend" } simple-ai-frontend = { path = "packages/frontend" } simple-ai-macros = { path = "packages/macros" } -dioxus = { version = "0.7.0-rc.1" } +dioxus = { version = "0.7.0" } [profile] diff --git a/packages/frontend/assets/style/entry.css b/packages/frontend/assets/style/entry.css index f04bc3c..7dc696e 100644 --- a/packages/frontend/assets/style/entry.css +++ b/packages/frontend/assets/style/entry.css @@ -1,100 +1,30 @@ -/* %%% entry.css %%% */ - -/* %% root %% */ :root { - /* % color palette % */ - --color-1: 17, 18, 19; - --color-2: 66, 69, 76; - --color-3: 95, 101, 115; - --color-4: 206, 214, 232; - --color-5: 250, 250, 250; - - --color-primary-50: #f7f8fc; - --color-primary-100: #ecedf8; - --color-primary-200: #d9daf2; - --color-primary-300: #bbbde8; - --color-primary-400: #767bd0; - --color-primary-500: #3c42af; - --color-primary-600: #2b2f7d; - --color-primary-700: #1f225b; - --color-primary-800: #121435; - --color-primary-900: #090a1b; - --color-primary-950: #05060f; - --color-secondary-50: #f8f7fc; - --color-secondary-100: #eeecf8; - --color-secondary-200: #ddd9f2; - --color-secondary-300: #c1bbe8; - --color-secondary-400: #8476d0; - --color-secondary-500: #4d3caf; - --color-secondary-600: #372b7d; - --color-secondary-700: #281f5b; - --color-secondary-800: #171235; - --color-secondary-900: #0c091b; - --color-secondary-950: #07050f; - --color-tertiary-50: #f9f7fc; - --color-tertiary-100: #f0ecf8; - --color-tertiary-200: #e2d9f2; - --color-tertiary-300: #cabbe8; - --color-tertiary-400: #9676d0; - --color-tertiary-500: #643caf; - --color-tertiary-600: #482b7d; - --color-tertiary-700: #341f5b; - --color-tertiary-800: #1e1235; - --color-tertiary-900: #0f091b; - --color-tertiary-950: #09050f; - --color-quaternary-50: #fcf7fc; - --color-quaternary-100: #f8ecf7; - --color-quaternary-200: #f2d9ee; - --color-quaternary-300: #e8bbe1; - --color-quaternary-400: #d076c3; - --color-quaternary-500: #af3c9e; - --color-quaternary-600: #7d2b71; - --color-quaternary-700: #5b1f52; - --color-quaternary-800: #351230; - --color-quaternary-900: #1b0918; - --color-quaternary-950: #0f050e; - --color-quinary-50: #fafcf7; - --color-quinary-100: #f2f8ec; - --color-quinary-200: #e4f2d9; - --color-quinary-300: #cfe8bb; - --color-quinary-400: #9fd076; - --color-quinary-500: #70af3c; - --color-quinary-600: #507d2b; - --color-quinary-700: #3a5b1f; - --color-quinary-800: #223512; - --color-quinary-900: #111b09; - --color-quinary-950: #0a0f05; - - /* color with names */ - --color-bg: var(--color-primary-950); - --color-text-fg: var(--color-primary-50); - --color-text-fg-ll: var(--color-primary-400); - --color-icon-bg: var(--color-primary-700); - --color-box-bg: var(--color-primary-700); - --color-border-ll: var(--color-primary-800); - --color-delete-fg: var(--color-quaternary-400); - --color-delete-bg: var(--color-quaternary-500); - --color-edit-fg: var(--color-primary-400); - --color-edit-bg: var(--color-primary-400); - --color-ok-fg: var(--color-quinary-400); - --color-ok-bg: var(--color-quinary-500); + --color-1: 200, 50%; + --color-2: 0, 50%; + --color-3: 40, 50%; + --color-4: 180, 50%; + + --color-bg: var(--color-1), 5%; + --color-text-fg: var(--color-1), 90%; + --color-box-fg: var(--color-1), 90%; + --color-box-bg: var(--color-1), 20%; + --color-button-fg: var(--color-box-fg); + --color-button-bg: var(--color-box-bg); + --color-icon-bg: transparent; + --color-icon-fg: var(--color-button-fg); + + --color-delete-fg: var(--color-2), 90%; + --color-delete-bg: var(--color-2), 30%; + --color-edit-fg: var(--color-3), 90%; + --color-edit-bg: var(--color-3), 30%; + --color-ok-fg: var(--color-4), 90%; + --color-ok-bg: var(--color-4), 30%; - /* % colors legacy % */ - --background-color: var(--color-1); - --main-color: var(--color-4); - --text-color: var(--color-5); - --accent-color: var(--color-3); - --background-highlight-color: var(--color-2); - - /* % border % */ --border-width: 0.15rem; --border-color: var(--accent-color); --border-radius: 1rem; } -/* %% general %% */ - -/* % full size % */ html, body, #main, @@ -136,12 +66,13 @@ p { } html { - background-color: rgb(var(--background-color)); - color: rgb(var(--text-color)); + background-color: hsl(var(--color-bg)); + color: hsl(var(--color-text-fg)); } .Icon { - color: rgb(var(--accent-color)); + color: hsl(var(--color-icon-fg)); + background-color: hsl(var(--color-icon-bg)); width: 2rem; height: 2rem; margin: 0.5rem; @@ -149,35 +80,36 @@ html { } button { - padding: 1rem; - margin: 1rem; + padding: 0.5rem; display: flex; - background-color: var(--color-primary-700); + background-color: hsl(var(--color-button-bg)); + color: hsl(var(--color-button-fg)); justify-content: center; align-items: center; - border-radius: 2rem; + border-radius: 3rem; + border: none; cursor: pointer; } -.NavbarButton { - color: rgb(var(--accent-color)); - display: flex; - justify-content: space-between; - flex-direction: column; - align-items: center; -} - -.NavbarButton p { - display: flex; - justify-content: center; - align-items: center; - margin: 0; -} +/* .NavbarButton { */ +/* color: rgb(var(--accent-color)); */ +/* display: flex; */ +/* justify-content: space-between; */ +/* flex-direction: column; */ +/* align-items: center; */ +/* } */ +/**/ +/* .NavbarButton p { */ +/* display: flex; */ +/* justify-content: center; */ +/* align-items: center; */ +/* margin: 0; */ +/* } */ /* % a % */ a { text-decoration: none; - color: rgb(var(--text-color)); + color: hsl(var(--color-link-fg)); } /* % h1 % */ diff --git a/packages/frontend/assets/style/items/navigation_link.css b/packages/frontend/assets/style/items/navigation_link.css new file mode 100644 index 0000000..3eeacca --- /dev/null +++ b/packages/frontend/assets/style/items/navigation_link.css @@ -0,0 +1,13 @@ +.NavigationLink { + flex-direction: column; + display: flex; + align-items: center; + gap: 1.5rem; + justify-content: space-between; +} + +.NavigationLink > button { +} + +.NavigationLink > label { +} diff --git a/packages/frontend/assets/style/items/project.css b/packages/frontend/assets/style/items/project.css index efecf58..76e5e50 100644 --- a/packages/frontend/assets/style/items/project.css +++ b/packages/frontend/assets/style/items/project.css @@ -9,13 +9,13 @@ margin: 1rem 0; display: flex; flex-direction: row; - background-color: var(--color-box-bg); + background-color: hsl(var(--color-box-bg)); border-radius: var(--project-border-radius); gap: 1rem; } .Project .divider { - background-color: var(--color-text-fg-ll); + background-color: hsl(var(--color-text-fg)); width: 0.1rem; } @@ -41,7 +41,7 @@ } .Project .actions .Icon { flex-grow: 1; - color: var(--color-text-fg); + color: hsl(var(--color-text-fg)); margin: 0; } @@ -67,20 +67,20 @@ } .Project .actions .open { - background-color: var(--color-ok-bg); + background-color: hsl(var(--color-ok-bg)); } .Project .actions .open:hover { - background-color: var(--color-ok-bg); + background-color: hsl(var(--color-ok-bg)); } .Project .actions .edit { - background-color: var(--color-edit-bg); + background-color: hsl(var(--color-edit-bg)); } .Project .actions .edit:hover { - background-color: var(--color-edit-bg); + background-color: hsl(var(--color-edit-bg)); } .Project .actions .delete { - background-color: var(--color-delete-bg); + background-color: hsl(var(--color-delete-bg)); } .Project .actions .delete:hover { - background-color: var(--color-delete-bg); + background-color: hsl(var(--color-delete-bg)); } diff --git a/packages/frontend/assets/style/items/top_nav.css b/packages/frontend/assets/style/items/top_nav.css index 838761b..4d2357b 100644 --- a/packages/frontend/assets/style/items/top_nav.css +++ b/packages/frontend/assets/style/items/top_nav.css @@ -2,6 +2,6 @@ width: 100%; display: flex; flex-direction: row; - background-color: rgba(var(--color-2), 0.3); + background-color: hsl(var(--color-box-bg)); z-index: 100; } diff --git a/packages/frontend/assets/themes/icons/con1/components/search/sample.svg b/packages/frontend/assets/themes/icons/con1/components/search/sample.svg deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/icons/con1/components/searchresult/sample.svg b/packages/frontend/assets/themes/icons/con1/components/searchresult/sample.svg deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/icons/con1/index.theme b/packages/frontend/assets/themes/icons/con1/index.theme deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/icons/con1/pages/editor/sample.svg b/packages/frontend/assets/themes/icons/con1/pages/editor/sample.svg deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/icons/con1/pages/new/sample.svg b/packages/frontend/assets/themes/icons/con1/pages/new/sample.svg deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/icons/con1/pages/search/sample.svg b/packages/frontend/assets/themes/icons/con1/pages/search/sample.svg deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/icons/con1/pages/start/editor.svg b/packages/frontend/assets/themes/icons/con1/pages/start/editor.svg deleted file mode 100644 index 999a769..0000000 --- a/packages/frontend/assets/themes/icons/con1/pages/start/editor.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/frontend/assets/themes/icons/con1/pages/start/new.svg b/packages/frontend/assets/themes/icons/con1/pages/start/new.svg deleted file mode 100644 index f4ce732..0000000 --- a/packages/frontend/assets/themes/icons/con1/pages/start/new.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/frontend/assets/themes/icons/con1/pages/start/search.svg b/packages/frontend/assets/themes/icons/con1/pages/start/search.svg deleted file mode 100644 index b77c31c..0000000 --- a/packages/frontend/assets/themes/icons/con1/pages/start/search.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/frontend/assets/themes/styles/cybr1/colors.css b/packages/frontend/assets/themes/styles/cybr1/colors.css deleted file mode 100644 index 34a5c31..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/colors.css +++ /dev/null @@ -1,44 +0,0 @@ -:root { - --color-1: rgb(173, 249, 236); - --color-2: rgb(184, 224, 172); - --color-3: rgb(223, 178, 117); - --color-4: rgb(237, 150, 119); - - --color-1-shaded-1: rgb(83, 158, 159); - --color-1-shaded-2: rgb(0, 92, 93); - --color-1-shaded-3: rgb(0, 33, 34); - - --color-2-shaded-1: rgb(102, 139, 92); - --color-2-shaded-2: rgb(46, 81, 38); - --color-2-shaded-3: rgb(0, 30, 0); - - --color-3-shaded-1: rgb(145, 107, 50); - --color-3-shaded-2: rgb(89, 59, 0); - --color-3-shaded-3: rgb(46, 14, 0); - - --color-4-shaded-1: rgb(162, 84, 57); - --color-4-shaded-2: rgb(107, 38, 15); - --color-4-shaded-3: rgb(60, 0, 0); - - --font-color: white; - --background-color: rgba(0, 33, 34, 0.8); - - --element-background-color: var(--background-color); - --element-border-color: var(--color-4-shaded-1); - --element-grayed-out: var(--color-1-shaded-2); - --element-font-color: var(--font-color); - - --button-background-color: var(--element-background-color); - --button-border-color: var(--element-border-color); - --button-font-color: var(--element-font-color); - - --input-background-color: var(--element-background-color); - --input-border-color: var(--element-border-color); - --input-grayed-out: var(--element-grayed-out); - --input-font-color: var(--element-font-color); - - --icon-color: var(--element-border-color); - - --connection-color: var(--element-border-color); - --connection-stroke-color: var(--element-border-color); -} diff --git a/packages/frontend/assets/themes/styles/cybr1/components/connection/config.css b/packages/frontend/assets/themes/styles/cybr1/components/connection/config.css deleted file mode 100644 index 64b29f0..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/components/connection/config.css +++ /dev/null @@ -1,7 +0,0 @@ -:root { - --connection-diameter: 1rem; - --connection-position: calc( - var(--connection-diameter) / -2 - var(--node-border-width) / 2 - ); - --connection-stroke-width: var(--element-border-width); -} diff --git a/packages/frontend/assets/themes/styles/cybr1/components/connection/index.css b/packages/frontend/assets/themes/styles/cybr1/components/connection/index.css deleted file mode 100644 index 584bda1..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/components/connection/index.css +++ /dev/null @@ -1,13 +0,0 @@ -.Connection > .Input, -.Connection > .Output { - width: var(--connection-diameter); - height: var(--connection-diameter); - background-color: var(--connection-color); - border-radius: 100%; -} - -.Draw > .Curve { - fill: none; - stroke: var(--connection-stroke-color); - stroke-width: var(--connection-stroke-width); -} diff --git a/packages/frontend/assets/themes/styles/cybr1/components/divider/config.css b/packages/frontend/assets/themes/styles/cybr1/components/divider/config.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/styles/cybr1/components/divider/index.css b/packages/frontend/assets/themes/styles/cybr1/components/divider/index.css deleted file mode 100644 index ee01955..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/components/divider/index.css +++ /dev/null @@ -1,22 +0,0 @@ -.Divider -{ - height: 100%; - width: 100%; -} - -.Divider > .wrapper -{ - all: revert; - height: 100%; - width: 1rem; - display: flex; - justify-content: center; - margin: 0 -0.44rem; -} - -.Divider > .wrapper > .inner -{ - height: 100%; - width: 0.12em; - background-color: white; -} \ No newline at end of file diff --git a/packages/frontend/assets/themes/styles/cybr1/components/node/config.css b/packages/frontend/assets/themes/styles/cybr1/components/node/config.css deleted file mode 100644 index 4e4e552..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/components/node/config.css +++ /dev/null @@ -1,9 +0,0 @@ -:root { - --node-width: 10rem; - --node-header-height: 4rem; - --node-footer-height: 4rem; - --node-margin: var(--param-connection-diameter); - - --node-border-width: var(--element-border-width); - --node-border-radius: var(--element-border-radius); -} diff --git a/packages/frontend/assets/themes/styles/cybr1/components/node/index.css b/packages/frontend/assets/themes/styles/cybr1/components/node/index.css deleted file mode 100644 index 6a94324..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/components/node/index.css +++ /dev/null @@ -1,33 +0,0 @@ -.Node { - width: var(--node-width); - height: fit-content; - border-width: var(--node-border-width); - border-radius: var(--node-border-radius); - border-style: solid; - border-color: var(--node-border-color); - display: flex; - flex-direction: column; - margin: var(--node-margin); - overflow: visible; -} - -.Node > main { - overflow: inherit; -} - -.Node > header { - width: 100%; - height: var(--node-header-height); - border-bottom: inherit; - border-color: inherit; - display: flex; - align-items: center; - justify-content: center; -} - -.Node > footer { - border-top: inherit; - border-color: inherit; - width: 100%; - height: var(--node-footer-height); -} diff --git a/packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/config.css b/packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/config.css deleted file mode 100644 index 29fdf48..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/config.css +++ /dev/null @@ -1,7 +0,0 @@ -:root { - --param-margin: 1rem; - --param-height: 3rem; - --border-width: var(--node-border-width); - --param-border-color: var(--node-border-color); - --param-margin: var(--param-margin); -} diff --git a/packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/index.css b/packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/index.css deleted file mode 100644 index cfce3f9..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/components/runtimeparam/index.css +++ /dev/null @@ -1,9 +0,0 @@ -.Param { - height: var(--param-height); - border-top-style: solid; - border-bottom-style: solid; - border-width: var(--param-border-width); - border-color: var(--param-border-color); - margin: var(--param-margin) 0; - overflow: visible; -} diff --git a/packages/frontend/assets/themes/styles/cybr1/components/search/config.css b/packages/frontend/assets/themes/styles/cybr1/components/search/config.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/styles/cybr1/components/search/index.css b/packages/frontend/assets/themes/styles/cybr1/components/search/index.css deleted file mode 100644 index 4a4f515..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/components/search/index.css +++ /dev/null @@ -1,83 +0,0 @@ -.Search -{ - display: flex; - flex-direction: column; - justify-content: flex-start; - overflow: scroll; -} - -.Search > main -{ - overflow: visible; - display : flex; - flex-direction : row; - flex-wrap : wrap; - justify-content : space-around; - height: 100%; - width: 100%; -} - -.Search > header -{ - align-items : center; - display : flex; - flex-direction : column; - justify-content : space-evenly; - position : sticky; - top : 1.5rem; - width : 78%; -} - -header > input -{ - width : 100%; -} - -header > input:focus -{ - transform : scale(1.1); - margin: 0; -} - -nav -{ - align-items : center; - display : flex; - flex-direction : row; - justify-content : space-around; - width : 70%; -} - -nav > * -{ - margin : 1rem; -} - -.Search > main > section -{ - all : revert; - display : flex; - flex-wrap : wrap; - justify-content : space-around; - margin : 0; -} - -.SearchResult -{ - height : 15rem; - margin : 1rem; - padding : 2rem; - width : 30rem; -} - -.spacer -{ - height : 6rem; -} - -aside -{ - margin: 0; - padding: 0; -} - diff --git a/packages/frontend/assets/themes/styles/cybr1/components/searchresult/config.css b/packages/frontend/assets/themes/styles/cybr1/components/searchresult/config.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/styles/cybr1/components/searchresult/index.css b/packages/frontend/assets/themes/styles/cybr1/components/searchresult/index.css deleted file mode 100644 index 2d015bd..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/components/searchresult/index.css +++ /dev/null @@ -1,107 +0,0 @@ -/*------------------------ SEARCH RESULT ------------------------*/ - - - -.SearchResult -{ - background-color: var(--i-sr-background-color); - border-color: var(--i-sr-border-color); - border-radius: var(--i-sr-border-radius); - border-style: solid; - border-width: 0.13em; - display: flex; - flex-direction: column; - margin: 1em; - padding: 1em; -} - -/*------------------------ ICONS ------------------------*/ -i -{ - color: var(--i-sr-icon-color); - cursor: pointer; - font-size: 1.4em; - margin-left: 0.3em; -} -/*.i.wrapper*/ -/*{*/ -/* align-self: start;*/ -/* justify-content: right;*/ -/*}*/ - -#open -{ - position: relative; - top: 0.04em; -} - -/*------------------------ LIST ------------------------*/ -li -{ - list-style: none; -} - -/*------------------------ H ------------------------*/ -/*------ H3 ------*/ -h3 -{ - margin: 0 0.5em 0 0; -} -/*------ SPAN ------*/ -#id -{ - color: var(--i-page-light-color); -} - -/*------ H5 ------*/ -h5 -{ - color: var(--i-page-light-color); - margin: 0; - padding: 0; -} - -/*!*------------------------ A ------------------------*!*/ -/*a*/ -/*{*/ -/* background-color: var(--i-sr-link-background-color);*/ -/* border-color: var(--i-sr-link-border-color);*/ -/* border-radius: var(--i-sr-link-border-radius);*/ -/* border-style: solid;*/ -/* color: var(--i-page-link-color);*/ -/* text-decoration: none;*/ -/* margin: 0 0 0 0.5em;*/ -/* padding: 0.1em 0.5em;*/ -/*}*/ - -.a.wrapper -{ - justify-content: left; -} - -/*------------------------ DIV ------------------------*/ -.wrapper -{ - all: revert; - display: flex; /*align-items: center;*/ - flex-direction: row; -} - -.wrapper.items -{ - width: 100%; - justify-content: space-between; -} - -/*------------------------ ADRESS ------------------------*/ -address -{ - color: var(--i-page-light-color); -} - - - -#open -{ - mask : url("../../icons/new_1.svg") center/contain no-repeat; -} diff --git a/packages/frontend/assets/themes/styles/cybr1/components/staticparam/config.css b/packages/frontend/assets/themes/styles/cybr1/components/staticparam/config.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/styles/cybr1/components/staticparam/index.css b/packages/frontend/assets/themes/styles/cybr1/components/staticparam/index.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/styles/cybr1/config.css b/packages/frontend/assets/themes/styles/cybr1/config.css deleted file mode 100644 index ff0f0d3..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/config.css +++ /dev/null @@ -1,19 +0,0 @@ -:root { - --border-width: 0.2rem; - - /* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BUTTON ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */ - --button-border-width: var(--border-width); - --button-border-radius: 2rem; - --button-box-shadow: 0.2em 0.4em 0.3em rgba(0, 0, 0, 0.4); - --button-box-shadow-animated: 0.4em 0.8em 0.8em rgba(0, 0, 0, 0.5); - --button-hover-scale: scale(1.05); - - --element-border-radius: 2rem; - --element-border-width: 0.2rem; - - --param-margin: 1rem; - --param-connection-diameter: 1rem; - --param-connection-margin: calc( - var(--param-connection-diameter) / -2 - var(--node-border-width) / 2 - ); -} diff --git a/packages/frontend/assets/themes/styles/cybr1/icon.ico b/packages/frontend/assets/themes/styles/cybr1/icon.ico deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/styles/cybr1/index.css b/packages/frontend/assets/themes/styles/cybr1/index.css deleted file mode 100644 index 41cc5dd..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/index.css +++ /dev/null @@ -1,230 +0,0 @@ -/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ HTML ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */ - -html { - border-width: var(--border-width); - color: var(--font-color); - - background: radial-gradient( - circle at center, - rgba(0, 33, 34, 0.8) 0%, - rgba(0, 29, 30, 0.85) 30%, - rgba(0, 25, 26, 0.9) 50%, - rgba(0, 21, 22, 0.95) 70%, - rgba(0, 17, 18, 0.98) 90%, - rgba(0, 13, 14, 0.99) 100% - ); -} - -/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ NO PADDING & MARGINS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */ - -html, -body, -main, -article, -section, -div, -h1, -h2, -h3 { - margin: 0; - padding: 0; -} - -/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ CENTER & FULL SIZE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */ - -html, -body, -main, -article, -section, -div { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} - -/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BUTTON ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */ - -button { - /* ━━━━━━━━━ BEAUTY ━━━━━━━━━ */ - background-color: var(--button-background-color); - color: var(--button-font-color); - box-shadow: var(--button-box-shadow); - - /* ━━━━━━━━━ BORDER ━━━━━━━━━ */ - border-style: solid; - border-width: var(--button-border-width); - border-color: var(--button-border-color); - border-radius: var(--button-border-radius); - - /* ━━━━━━━━━ LAYOUT ━━━━━━━━━ */ - padding: 0.8em; - - /* ━━━━━━━━━ ANIMATION ━━━━━━━━━ */ - transition: - box-shadow 0.3s ease, - transform 0.3s ease; -} - -button:hover { - /* ━━━━━━━━━ BEAUTY ━━━━━━━━━ */ - box-shadow: var(--button-box-shadow-animated); - - /* ━━━━━━━━━ ANIMATION ━━━━━━━━━ */ - transform: var(--button-hover-scale); -} - -button:focus { - /* ━━━━━━━━━ BEAUTY ━━━━━━━━━ */ - outline: none; -} - -/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ FORM ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */ - -form { - align-items: center; - display: flex; - flex-direction: column; - height: 80%; - justify-content: space-around; - width: 90%; -} - -/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ INPUT ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */ - -input, -.LabeledBox { - border-width: var(--border-width); - background-color: var(--input-background-color); - border-color: var(--input-border-color-animated); - --input-border-color-animated: var(--input-grayed-out); - border-radius: 1em; - border-style: solid; - box-shadow: 0.2em 0.4em 0.3em rgba(0, 0, 0, 0.4); - caret-shape: block; - color: var(--input-font-color); - height: 3rem; - transition: - box-shadow 0.5s ease, - transform 0.5s ease, - margin 0.5s ease, - padding 0.5s ease, - height 0.5s ease, - border-color 0.5s ease; - width: 80%; -} - -input:hover, -.LabeledBox:hover { - box-shadow: 0.4em 0.8em 0.8em rgba(0, 0, 0, 0.5); - transform: scale(1.1); -} - -input:focus, -.LabeledBox:has(> input:focus) { - border-color: var(--input-border-color-animated); - --input-border-color-animated: var(--input-border-color); - box-shadow: 0.4em 0.8em 0.8em rgba(0, 0, 0, 0.5); - height: 4rem; - margin: 2rem 0; - outline: none; - transform: scale(1.2); -} - -input::placeholder { - color: var(--input-grayed-out); - font-style: italic; - transition: color 0.5s ease; -} - -input:focus::placeholder { - color: gray; -} - -input { - width: calc(80% - 2rem); - padding: 0 1rem; -} - -input::-webkit-file-upload-button { - height: 100%; - width: 100%; - background-color: transparent; - border: none; - color: var(--input-grayed-out); - font-weight: bold; -} - -input[type="range"] { - -webkit-appearance: none; - height: 10px; - border-radius: 5px; - background: transparent; - outline: none; -} - -input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - width: 20px; - height: 20px; - border-radius: 50%; - background: var(--input-border-color-animated); - cursor: pointer; - transition: background-color 0.5s ease; -} - -input[type="range"]::-webkit-slider-runnable-track { - width: 100%; - height: 10px; - border-radius: 5px; - background: transparent; - display: flex; - align-items: center; -} - -input[type="range"]::-webkit-slider-thumb:focus { - background-color: var(--input-border-color-animated); -} - -.LabeledBox > input { - all: revert; - background-color: transparent; - border-color: transparent; - caret-color: var(--input-border-color-animated); - color: var(--input-font-color); - flex-grow: 1; - padding: 1rem; -} - -.LabeledBox > input:focus { - caret-color: var(--input-border-color-animated); - outline: none; -} - -.LabeledBox > .divider { - background-color: var(--input-border-color-animated); - height: 100%; - transition: background-color 0.5s ease; - width: var(--border-width); -} - -/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ LABEL ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */ -label { - color: var(--input-border-color-animated); - font-weight: bold; - padding: 1em; - transition: color 0.5s ease; -} - -div.icon { - width: 2rem; - height: 2rem; - color: var(--icon-color); -} - -.Icon { - width: 2rem; - height: 2rem; -} diff --git a/packages/frontend/assets/themes/styles/cybr1/index.theme b/packages/frontend/assets/themes/styles/cybr1/index.theme deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/editor/config.css b/packages/frontend/assets/themes/styles/cybr1/pages/editor/config.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/editor/index.css b/packages/frontend/assets/themes/styles/cybr1/pages/editor/index.css deleted file mode 100644 index 1b757d8..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/pages/editor/index.css +++ /dev/null @@ -1,36 +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%; -} - diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/new/config.css b/packages/frontend/assets/themes/styles/cybr1/pages/new/config.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/new/index.css b/packages/frontend/assets/themes/styles/cybr1/pages/new/index.css deleted file mode 100644 index 7ceb6fb..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/pages/new/index.css +++ /dev/null @@ -1,14 +0,0 @@ -@import "../index.css"; - - -button -{ - width: 30%; - margin-top: 2em; - border-color: var(--color-2-shaded-1) -} - -input -{ - -} diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/search/config.css b/packages/frontend/assets/themes/styles/cybr1/pages/search/config.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/search/index.css b/packages/frontend/assets/themes/styles/cybr1/pages/search/index.css deleted file mode 100644 index 2e0c7cf..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/pages/search/index.css +++ /dev/null @@ -1,3 +0,0 @@ -@import "../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 deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/themes/styles/cybr1/pages/start/index.css b/packages/frontend/assets/themes/styles/cybr1/pages/start/index.css deleted file mode 100644 index 36941bc..0000000 --- a/packages/frontend/assets/themes/styles/cybr1/pages/start/index.css +++ /dev/null @@ -1,8 +0,0 @@ -/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SECTION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */ - -section { - display: flex; - height: fit-content; - justify-content: space-evenly; - width: 20rem; -} diff --git a/packages/frontend/src/modules/components.rs b/packages/frontend/src/modules/components.rs index f6c6d6d..f8f493d 100644 --- a/packages/frontend/src/modules/components.rs +++ b/packages/frontend/src/modules/components.rs @@ -11,6 +11,7 @@ pub mod focus_button_array; pub mod heading_layout; pub mod labeled_box; pub mod nav_button; +pub mod navigation_link; pub mod navigation_page; pub mod node; pub mod project; @@ -34,6 +35,7 @@ pub mod prelude { pub use super::heading_layout::*; pub use super::labeled_box::*; pub use super::nav_button::*; + pub use super::navigation_link::*; pub use super::navigation_page::*; pub use super::node::*; pub use super::project; diff --git a/packages/frontend/src/modules/components/nav_button.rs b/packages/frontend/src/modules/components/nav_button.rs index 7cd3f2e..a1e19da 100644 --- a/packages/frontend/src/modules/components/nav_button.rs +++ b/packages/frontend/src/modules/components/nav_button.rs @@ -1,11 +1,6 @@ -// %%% components / nav_button.rs %%% - -// %% includes %% use super::utils::*; use dioxus::{core::AttributeValue, router::NavigationTarget}; -// %% main %% - #[item] pub fn NavButton( children: Element, diff --git a/packages/frontend/src/modules/components/navigation_link.rs b/packages/frontend/src/modules/components/navigation_link.rs new file mode 100644 index 0000000..f69e59d --- /dev/null +++ b/packages/frontend/src/modules/components/navigation_link.rs @@ -0,0 +1,19 @@ +use super::utils::*; +use dioxus::{core::AttributeValue, router::NavigationTarget}; + +#[item] +pub fn NavigationLink( + #[props(into)] to: NavigationTarget, + #[props(default)] label: String, + children: Element, +) -> Element { + rsx! { + div { + button { + Link { to, {children} } + } + label { "{label}" } + } + + } +} diff --git a/packages/frontend/src/modules/components/top_nav.rs b/packages/frontend/src/modules/components/top_nav.rs index 0715e13..3f6f3d4 100644 --- a/packages/frontend/src/modules/components/top_nav.rs +++ b/packages/frontend/src/modules/components/top_nav.rs @@ -1,16 +1,29 @@ use super::utils::*; use super::{breadcrumbs::Breadcrumbs, window_decorations::WindowDecorations}; +#[cfg(not(target_family = "wasm"))] +use dioxus::desktop::window; + +#[cfg(not(target_family = "wasm"))] #[item] pub fn TopNav() -> Element { rsx! { - div { + div { onmousedown: move |_| { window().drag() }, Breadcrumbs {} WindowDecorations {} } } } +#[cfg(target_family = "wasm")] +#[item] +pub fn TopNav() -> Element { + rsx! { + div { + } + } +} + #[item] pub fn TopNavLayout() -> Element { rsx! { diff --git a/packages/frontend/src/modules/components/window_decorations.rs b/packages/frontend/src/modules/components/window_decorations.rs index eb98b98..8763596 100644 --- a/packages/frontend/src/modules/components/window_decorations.rs +++ b/packages/frontend/src/modules/components/window_decorations.rs @@ -1,13 +1,29 @@ -// %%% components / window_decorations.rs %%% -// %% includes %% use super::utils::*; -// %% main %% +#[cfg(not(target_family = "wasm"))] +use dioxus::desktop::window; + +use dioxus::core::AttributeValue; + +#[cfg(not(target_family = "wasm"))] +#[item] +pub fn WindowDecorations() -> Element { + rsx! { + div { + section { + article { class: "wrapper", onclick: |_| { window().close() }, CloseIcon {} } + } + } + } +} + +#[cfg(target_family = "wasm")] #[item] pub fn WindowDecorations() -> Element { rsx! { div { - section { CloseIcon {} } + section { + } } } } diff --git a/packages/frontend/src/modules/icons.rs b/packages/frontend/src/modules/icons.rs index 63e42c8..7780211 100644 --- a/packages/frontend/src/modules/icons.rs +++ b/packages/frontend/src/modules/icons.rs @@ -1,9 +1,5 @@ -// %%% icons.rs %%% - -// %% includes %% use crate::utils::*; -// %% main %% icon! { settings: , folder_open: , diff --git a/packages/frontend/src/modules/pages/start.rs b/packages/frontend/src/modules/pages/start.rs index 222456e..99780f4 100644 --- a/packages/frontend/src/modules/pages/start.rs +++ b/packages/frontend/src/modules/pages/start.rs @@ -9,14 +9,8 @@ pub fn Start() -> Element { rsx! { main { NavigationPage { - NavButton { class: "editor", to: Route::Projects {}, - ProjectsIcon {} - p { "projects" } - } - NavButton { class: "new", to: Route::New {}, - NewIcon {} - p { "new project" } - } + NavigationLink { to: Route::Projects {}, label: "projects", ProjectsIcon {} } + NavigationLink { to: Route::New {}, label: "new project", NewIcon {} } } } } diff --git a/packages/macros/src/element/handler.rs b/packages/macros/src/element/handler.rs index 929813c..5eb720b 100644 --- a/packages/macros/src/element/handler.rs +++ b/packages/macros/src/element/handler.rs @@ -1,19 +1,11 @@ -// %%% ipl.rs %%% -// this is the implementation of the element macro, which is for handling the style assets and the -// creation of new dioxus components - -// %% includes %% -// % extern % use proc_macro2::{Span, TokenStream}; use quote::*; use syn::{parse2, parse_quote, LitStr}; -// % intern % use super::{ attrs::ElementAttrs, function::ElementFunction, kind::ElementKind, manifest::ElementConfig, }; -// %% main %% pub struct ElementHandler { pub attrs: ElementAttrs, pub function: ElementFunction, diff --git a/packages/macros/src/element/rsx_ast.rs b/packages/macros/src/element/rsx_ast.rs index db0a0c8..4e1675b 100644 --- a/packages/macros/src/element/rsx_ast.rs +++ b/packages/macros/src/element/rsx_ast.rs @@ -71,7 +71,6 @@ impl ToTokens for Element { } } -// % Attribute % #[derive(Clone)] pub struct Attribute { pub spread: Option, @@ -98,7 +97,7 @@ impl Parse for Attribute { }; let value: Option = if colon.is_some() { - Some(input.parse()?) + input.parse().ok() } else { None }; diff --git a/packages/macros/src/entry.rs b/packages/macros/src/entry.rs index 4a2e3df..b2c9b49 100644 --- a/packages/macros/src/entry.rs +++ b/packages/macros/src/entry.rs @@ -1,14 +1,5 @@ -// %%% entry.rs %%% -// this is the extra implementation of element for the ElementKind::Entry type -// #[page] instead of #[element(type = ElementKind::Page)] - -// %% includes %% -// % intern % use crate::element::kind::*; -// % extern % use proc_macro2::TokenStream; -// %% main %% -// % impl % pub fn macro_impl(attr: TokenStream, item: TokenStream) -> TokenStream { ElementKind::Entry {}.extra_macro_impl(attr, item) } diff --git a/packages/macros/src/icon.rs b/packages/macros/src/icon.rs index 6ecbf03..e5f4011 100644 --- a/packages/macros/src/icon.rs +++ b/packages/macros/src/icon.rs @@ -80,8 +80,9 @@ impl Parse for FieldValues { pub fn macro_impl(item: TokenStream) -> TokenStream { let fields: FieldValues = parse2(item).unwrap(); let mut out: TokenStream = quote! { - use dioxus::prelude::*; + use dioxus::{core::AttributeValue, prelude::*}; }; + println!("ICON: --------------------------------------------------------------"); for field in fields.fields { let function_name = pascal_case(&format!("{}Icon", field.member)); let function_ident = Ident::new(&function_name, Span::call_site()); @@ -89,12 +90,25 @@ pub fn macro_impl(item: TokenStream) -> TokenStream { let svg_body = rsx_from_html(&svg_dom); let block = write_block_out(&svg_body).expect("failed writing block"); let svg = TokenStream::from_str(&block).unwrap(); + println!("svg: {}", svg); let classes = format!("Icon {}", function_name); quote! { - pub fn #function_ident() -> Element { + #[component] + pub fn #function_ident( + // #[props(extends = GlobalAttributes)] attributes: Vec + ) -> Element { + let mut other_classes = String::new(); + // if let Some(pos) = attributes.iter().position(|x| x.name == "class") { + // let value = attributes.remove(pos).value; + // if let AttributeValue::Text(text) = value { + // other_classes = text; + // } + // } + let class = format!("{}{other_classes}", #classes); rsx! { div { - class: #classes, + class, + // ..attributes, #svg } } @@ -102,5 +116,9 @@ pub fn macro_impl(item: TokenStream) -> TokenStream { } .to_tokens(&mut out); } + println!( + "\n -------------------------------------------- ICON OUT:\n{}", + out + ); out } From 6207ad16a6388c14842a75358ab30397ee3df0da Mon Sep 17 00:00:00 2001 From: = Date: Wed, 26 Nov 2025 21:25:14 +0100 Subject: [PATCH 10/17] working on editor.. --- packages/frontend/assets/scripts/viewport.js | 300 ++++++++++++++++++ packages/frontend/assets/style/entry.css | 8 +- .../frontend/assets/style/items/project.css | 7 +- .../frontend/assets/style/pages/editor.css | 10 +- .../frontend/assets/style/pages/projects.css | 7 + .../src/modules/components/project.rs | 2 +- packages/frontend/src/modules/pages/editor.rs | 31 +- .../frontend/src/modules/pages/project_nav.rs | 9 +- 8 files changed, 329 insertions(+), 45 deletions(-) create mode 100644 packages/frontend/assets/scripts/viewport.js diff --git a/packages/frontend/assets/scripts/viewport.js b/packages/frontend/assets/scripts/viewport.js new file mode 100644 index 0000000..14b96ad --- /dev/null +++ b/packages/frontend/assets/scripts/viewport.js @@ -0,0 +1,300 @@ +class VNode { + constructor(x, y, label, params) { + this.x = x; + this.y = y; + this.label = label; + // params: [{ type: "input" | "output", name: "param" }]; + this.inputs = params.filter((p) => p.type === "input"); + this.outputs = params.filter((p) => p.type === "output"); + this.paramSpacing = 32; + this.width = 160; + this.height = Math.max( + 48, + 32 + + Math.max(this.inputs.length, this.outputs.length) * this.paramSpacing, + ); + } + getInputCoords(i) { + return { + x: this.x, + y: this.y + 32 + i * this.paramSpacing, + }; + } + getOutputCoords(i) { + return { + x: this.x + this.width, + y: this.y + 32 + i * this.paramSpacing, + }; + } + contains(px, py) { + return ( + px > this.x && + px < this.x + this.width && + py > this.y && + py < this.y + this.height + ); + } + inputHit(px, py) { + for (let i = 0; i < this.inputs.length; ++i) { + let p = this.getInputCoords(i); + if (Math.hypot(px - p.x, py - p.y) < 14) return i; + } + return null; + } + outputHit(px, py) { + for (let i = 0; i < this.outputs.length; ++i) { + let p = this.getOutputCoords(i); + if (Math.hypot(px - p.x, py - p.y) < 14) return i; + } + return null; + } +} +class Connection { + constructor(fromNode, fromOutput, toNode, toInput) { + this.fromNode = fromNode; + this.fromOutput = fromOutput; + this.toNode = toNode; + this.toInput = toInput; + } +} + +const canvas = document.getElementById("draw"); +const viewport = document.getElementById("viewport"); +const ctx = canvas.getContext("2d"); + +const gridSpacing = 40; +const dotRadius = 2; +let zoom = 1; +let offsetX = 0, + offsetY = 0; + +let nodes = [ + new VNode(100, 100, "A", [{ type: "output", name: "outA" }]), + new VNode(400, 200, "B", [{ type: "input", name: "inB" }]), + new VNode(200, 320, "C", [ + { type: "input", name: "inC" }, + { type: "output", name: "outC" }, + { type: "output", name: "outC2" }, + ]), +]; + +let connections = []; +let mouse = { x: 0, y: 0 }; +let draggingNode = null, + dragOffsetX, + dragOffsetY; +let connectingFrom = null; // { node, outIdx } +let isPanning = false; +let panStart = { x: 0, y: 0 }, + panOrigin = { x: 0, y: 0 }; + +function fitNodeSizeToGrid(node) { + node.width = Math.ceil(node.width / gridSpacing) * gridSpacing; + node.height = Math.ceil(node.height / gridSpacing) * gridSpacing; +} + +function resize() { + const rect = viewport.getBoundingClientRect(); + canvas.width = rect.width; + canvas.height = rect.height; + draw(); +} +window.addEventListener("resize", resize); + +function toEditor(x, y) { + return { + x: (x - offsetX) / zoom, + y: (y - offsetY) / zoom, + }; +} + +function draw() { + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.setTransform(zoom, 0, 0, zoom, offsetX, offsetY); + + // Grid + ctx.fillStyle = "#3a3c40"; + const [w, h] = [ + (canvas.width - offsetX) / zoom, + (canvas.height - offsetY) / zoom, + ]; + const startX = Math.floor(-offsetX / zoom / gridSpacing) * gridSpacing; + const startY = Math.floor(-offsetY / zoom / gridSpacing) * gridSpacing; + for (let x = startX; x < w + gridSpacing; x += gridSpacing) + for (let y = startY; y < h + gridSpacing; y += gridSpacing) { + ctx.beginPath(); + ctx.arc(x, y, dotRadius, 0, 2 * Math.PI); + ctx.fill(); + } + + // Connections + for (let c of connections) { + const from = c.fromNode.getOutputCoords(c.fromOutput); + const to = c.toNode.getInputCoords(c.toInput); + ctx.strokeStyle = "#fb0"; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.moveTo(from.x, from.y); + ctx.bezierCurveTo(from.x + 40, from.y, to.x - 40, to.y, to.x, to.y); + ctx.stroke(); + } + // Live connect preview + if (connectingFrom) { + const from = connectingFrom.node.getOutputCoords(connectingFrom.outIdx); + ctx.strokeStyle = "#fb0"; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(from.x, from.y); + ctx.bezierCurveTo( + from.x + 40, + from.y, + mouse.x - 40, + mouse.y, + mouse.x, + mouse.y, + ); + ctx.stroke(); + } + + // Nodes + for (let n of nodes) { + fitNodeSizeToGrid(n); + + ctx.fillStyle = "#374565"; + ctx.strokeStyle = "#58a"; + ctx.lineWidth = 2; + ctx.fillRect(n.x, n.y, n.width, n.height); + ctx.strokeRect(n.x, n.y, n.width, n.height); + ctx.fillStyle = "#fff"; + ctx.font = "16px sans-serif"; + ctx.fillText(n.label, n.x + 10, n.y + 22); + + // Inputs (left) + n.inputs.forEach((input, i) => { + const p = n.getInputCoords(i); + ctx.beginPath(); + ctx.arc(p.x, p.y, 12, 0, 2 * Math.PI); + ctx.fillStyle = "#48e"; + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = "#fff"; + ctx.fillText(input.name, p.x + 16, p.y + 6); + }); + + // Outputs (right) + n.outputs.forEach((output, i) => { + const p = n.getOutputCoords(i); + ctx.beginPath(); + ctx.arc(p.x, p.y, 12, 0, 2 * Math.PI); + ctx.fillStyle = "#fa3"; + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = "#fff"; + ctx.fillText(output.name, p.x - 60, p.y + 6); + }); + } + + ctx.setTransform(1, 0, 0, 1, 0, 0); +} + +function add_listeners() { + viewport.addEventListener("mousedown", (e) => { + console.log("FIRED!"); + const p = toEditor(e.offsetX, e.offsetY); + mouse = p; + // output hit first (for drag to input) + for (let node of nodes) { + let outIdx = node.outputHit(p.x, p.y); + if (outIdx !== null) { + connectingFrom = { node, outIdx }; + return; + } + } + // hit test nodes + for (let node of nodes) { + if (node.contains(p.x, p.y)) { + draggingNode = node; + dragOffsetX = p.x - node.x; + dragOffsetY = p.y - node.y; + return; + } + } + // Panning + isPanning = true; + panOrigin.x = offsetX; + panOrigin.y = offsetY; + panStart.x = e.offsetX; + panStart.y = e.offsetY; + }); + + viewport.addEventListener("mousemove", (e) => { + const p = toEditor(e.offsetX, e.offsetY); + mouse = p; + if (draggingNode) { + let newX = Math.round((p.x - dragOffsetX) / gridSpacing) * gridSpacing; + let newY = Math.round((p.y - dragOffsetY) / gridSpacing) * gridSpacing; + draggingNode.x = newX; + draggingNode.y = newY; + fitNodeSizeToGrid(draggingNode); + draw(); + } else if (isPanning) { + offsetX = panOrigin.x + (e.offsetX - panStart.x); + offsetY = panOrigin.y + (e.offsetY - panStart.y); + draw(); + } else if (connectingFrom) { + draw(); + } + }); + + viewport.addEventListener("mouseup", (e) => { + const p = toEditor(e.offsetX, e.offsetY); + if (draggingNode) { + draggingNode = null; + draw(); + } else if (connectingFrom) { + for (let node of nodes) { + let inIdx = node.inputHit(p.x, p.y); + if (inIdx !== null && node !== connectingFrom.node) { + connections.push( + new Connection( + connectingFrom.node, + connectingFrom.outIdx, + node, + inIdx, + ), + ); + break; + } + } + connectingFrom = null; + draw(); + } else if (isPanning) { + isPanning = false; + draw(); + } + }); + + viewport.addEventListener("wheel", (e) => { + let scale = 1 + (e.deltaY < 0 ? 0.1 : -0.1); + let mx = e.offsetX, + my = e.offsetY; + const before = toEditor(mx, my); + zoom = Math.max(0.4, Math.min(2.5, zoom * scale)); + const after = toEditor(mx, my); + offsetX += (after.x - before.x) * zoom; + offsetY += (after.y - before.y) * zoom; + draw(); + e.preventDefault(); + }); +} + +function run() { + console.log("RUNNING"); + add_listeners(); + resize(); + draw(); +} + +window.run_editor = run; +window.run_editor(); diff --git a/packages/frontend/assets/style/entry.css b/packages/frontend/assets/style/entry.css index 7dc696e..7ebdb16 100644 --- a/packages/frontend/assets/style/entry.css +++ b/packages/frontend/assets/style/entry.css @@ -12,6 +12,8 @@ --color-button-bg: var(--color-box-bg); --color-icon-bg: transparent; --color-icon-fg: var(--color-button-fg); + --color-input-bg: var(--color-1); + --color-input-fg: var(--color-1); --color-delete-fg: var(--color-2), 90%; --color-delete-bg: var(--color-2), 30%; @@ -131,18 +133,20 @@ form { } input { - background-color: var(); + background-color: hsl(var(--color-input-bg), 15%); border-radius: 5rem; border-style: none; caret-shape: block; height: 3rem; width: 100%; box-sizing: border-box; - color: var(--color-primary-50); + color: hsl(var(--color-input-fg), 90%); padding: 1rem; + transition: background-color 0.5s ease-in-out; } input:focus { + background-color: hsl(var(--color-input-bg), 20%); outline: none; } diff --git a/packages/frontend/assets/style/items/project.css b/packages/frontend/assets/style/items/project.css index 76e5e50..98ec3aa 100644 --- a/packages/frontend/assets/style/items/project.css +++ b/packages/frontend/assets/style/items/project.css @@ -6,9 +6,9 @@ ); padding: var(--project-padding); - margin: 1rem 0; display: flex; flex-direction: row; + align-items: center; background-color: hsl(var(--color-box-bg)); border-radius: var(--project-border-radius); gap: 1rem; @@ -57,13 +57,10 @@ .Project .actions a { opacity: 0.7; scale: 1; - transition: - opacity 0.3s ease-in-out, - scale 0.1s ease-in-out; + transition: opacity 0.3s ease-in-out; } .Project .actions a:hover { opacity: 1; - scale: 1.1; } .Project .actions .open { diff --git a/packages/frontend/assets/style/pages/editor.css b/packages/frontend/assets/style/pages/editor.css index 32c8707..ea51af3 100644 --- a/packages/frontend/assets/style/pages/editor.css +++ b/packages/frontend/assets/style/pages/editor.css @@ -1,3 +1,9 @@ -aside { - width: 50rem; +#viewport { + display: flex; + flex-grow: 1; + overflow: hidden; +} + +#paint { + flex-grow: 1; } diff --git a/packages/frontend/assets/style/pages/projects.css b/packages/frontend/assets/style/pages/projects.css index f4b116e..1a238a6 100644 --- a/packages/frontend/assets/style/pages/projects.css +++ b/packages/frontend/assets/style/pages/projects.css @@ -1,4 +1,11 @@ .Projects { +} + +.Projects .projects-wrapper { + padding: 1rem 0; +} + +.Projects .projects-view { display: flex; flex-direction: column; gap: 1rem; diff --git a/packages/frontend/src/modules/components/project.rs b/packages/frontend/src/modules/components/project.rs index 19a599d..ac60af7 100644 --- a/packages/frontend/src/modules/components/project.rs +++ b/packages/frontend/src/modules/components/project.rs @@ -8,7 +8,7 @@ pub fn Project(name: String, date: DateTime, desc: String) -> Element { div { section { class: "infos", h3 { class: "name", {name} } - p { class: "date", {date.naive_local().date().to_string()} } + // p { class: "date", {date.naive_local().date().to_string()} } br { class: "spacer" } p { class: "desc", {desc} } } diff --git a/packages/frontend/src/modules/pages/editor.rs b/packages/frontend/src/modules/pages/editor.rs index af9c0a6..030181b 100644 --- a/packages/frontend/src/modules/pages/editor.rs +++ b/packages/frontend/src/modules/pages/editor.rs @@ -7,35 +7,12 @@ use std::collections::HashMap; // %% main %% #[page] pub fn Editor() -> Element { - let mut section_contents_map = use_signal(HashMap::new); - + document::eval(r#"console.log("hello world"); window.run_editor(); window.run_hello();"#); rsx! { main { - DragArea { - Divider { - section { Viewport {} } - aside { z_index: 2, - nav { - FocusButton { - onfocus: move || { - section_contents_map.write().insert("search", rsx! { - Search {} - "Hello" - }); - }, - onunfocus: move || { - section_contents_map.write().remove("search"); - }, - SearchIcon {} - } - } - section { - for (_ , e) in section_contents_map() { - {e} - } - } - } - } + section { id: "viewport", + canvas { id: "draw" } + document::Script { src: asset!("/assets/scripts/viewport.js"), defer: true } } } } diff --git a/packages/frontend/src/modules/pages/project_nav.rs b/packages/frontend/src/modules/pages/project_nav.rs index 83f2132..ab5d878 100644 --- a/packages/frontend/src/modules/pages/project_nav.rs +++ b/packages/frontend/src/modules/pages/project_nav.rs @@ -5,14 +5,7 @@ pub fn ProjectNav() -> Element { rsx! { main { NavigationPage { - NavButton { to: Route::Projects {}, - ProjectsIcon {} - p { "onnx-editor" } - } - NavButton { to: Route::Projects {}, - ProjectsIcon {} - p { "export" } - } + NavigationLink { to: Route::Editor {}, label: "onnx editor", ProjectsIcon {} } } } } From 8c1a6d45fcdd8b8e8edb8145ac31e6fc42fa48cd Mon Sep 17 00:00:00 2001 From: = Date: Fri, 28 Nov 2025 18:29:46 +0100 Subject: [PATCH 11/17] editor not showing the second time fix --- Cargo.toml | 2 +- packages/frontend/assets/scripts/viewport.js | 537 +++++++++++------- packages/frontend/src/modules/pages/editor.rs | 10 +- 3 files changed, 345 insertions(+), 204 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cadce60..df6cd3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ simple-ai-backend = { path = "packages/backend" } simple-ai-frontend = { path = "packages/frontend" } simple-ai-macros = { path = "packages/macros" } -dioxus = { version = "0.7.0" } +dioxus = { version = "0.7.1" } [profile] diff --git a/packages/frontend/assets/scripts/viewport.js b/packages/frontend/assets/scripts/viewport.js index 14b96ad..0ef4a43 100644 --- a/packages/frontend/assets/scripts/viewport.js +++ b/packages/frontend/assets/scripts/viewport.js @@ -1,11 +1,52 @@ +// A reusable, instantiable node-editor viewport module. +// Usage: +// import { Viewport, VNode, Parameter, Connection } from './viewport.js'; +// const vp = new Viewport(document.getElementById('editor')); +// vp.addNode(new VNode(100,100,'A',[new Parameter('output','outA')])); +// ... etc. + +class Parameter { + constructor(type = "input", name = "") { + this.type = type; // "input" or "output" + this.name = name; + // visual defaults (can be overridden externally) + this.radius = 12; + this.fillInput = "#48e"; + this.fillOutput = "#fa3"; + this.textColor = "#fff"; + } + + // draw the parameter socket and its label. `pos` is {x,y}. + draw(ctx, pos, isInput) { + ctx.beginPath(); + ctx.arc(pos.x, pos.y, this.radius, 0, 2 * Math.PI); + ctx.fillStyle = isInput ? this.fillInput : this.fillOutput; + ctx.fill(); + ctx.stroke(); + + ctx.fillStyle = this.textColor; + // Labels are drawn to the right for inputs, to the left for outputs + if (isInput) { + ctx.fillText(this.name, pos.x + 16, pos.y + 6); + } else { + ctx.fillText(this.name, pos.x - 60, pos.y + 6); + } + } +} + class VNode { - constructor(x, y, label, params) { + constructor(x = 0, y = 0, label = "Node", params = []) { this.x = x; this.y = y; this.label = label; - // params: [{ type: "input" | "output", name: "param" }]; - this.inputs = params.filter((p) => p.type === "input"); - this.outputs = params.filter((p) => p.type === "output"); + // params is array of Parameter instances or plain objects {type,name} + this.params = params.map((p) => + p instanceof Parameter ? p : new Parameter(p.type, p.name), + ); + this.inputs = this.params.filter((p) => p.type === "input"); + this.outputs = this.params.filter((p) => p.type === "output"); + + // visual defaults this.paramSpacing = 32; this.width = 160; this.height = Math.max( @@ -13,19 +54,28 @@ class VNode { 32 + Math.max(this.inputs.length, this.outputs.length) * this.paramSpacing, ); + + this.fillStyle = "#374565"; + this.strokeStyle = "#58a"; + this.titleColor = "#fff"; + this.titleFont = "16px sans-serif"; } + + // compute input socket coordinates getInputCoords(i) { return { x: this.x, y: this.y + 32 + i * this.paramSpacing, }; } + // compute output socket coordinates getOutputCoords(i) { return { x: this.x + this.width, y: this.y + 32 + i * this.paramSpacing, }; } + contains(px, py) { return ( px > this.x && @@ -34,6 +84,7 @@ class VNode { py < this.y + this.height ); } + inputHit(px, py) { for (let i = 0; i < this.inputs.length; ++i) { let p = this.getInputCoords(i); @@ -41,6 +92,7 @@ class VNode { } return null; } + outputHit(px, py) { for (let i = 0; i < this.outputs.length; ++i) { let p = this.getOutputCoords(i); @@ -48,218 +100,291 @@ class VNode { } return null; } + + // snap size to gridSpacing + fitToGrid(gridSpacing) { + this.width = Math.ceil(this.width / gridSpacing) * gridSpacing; + this.height = Math.ceil(this.height / gridSpacing) * gridSpacing; + } + + // draw the whole node including its parameters (uses Parameter.draw) + draw(ctx, viewport) { + // Ensure node size aligns to grid for consistent visuals + this.fitToGrid(viewport.gridSpacing); + + ctx.fillStyle = this.fillStyle; + ctx.strokeStyle = this.strokeStyle; + ctx.lineWidth = 2; + ctx.fillRect(this.x, this.y, this.width, this.height); + ctx.strokeRect(this.x, this.y, this.width, this.height); + + ctx.fillStyle = this.titleColor; + ctx.font = this.titleFont; + ctx.fillText(this.label, this.x + 10, this.y + 22); + + // draw inputs (left) + this.inputs.forEach((input, i) => { + const p = this.getInputCoords(i); + input.draw(ctx, p, true); + }); + + // draw outputs (right) + this.outputs.forEach((output, i) => { + const p = this.getOutputCoords(i); + output.draw(ctx, p, false); + }); + } } + class Connection { constructor(fromNode, fromOutput, toNode, toInput) { this.fromNode = fromNode; this.fromOutput = fromOutput; this.toNode = toNode; this.toInput = toInput; + + // visual defaults + this.color = "#fb0"; + this.width = 3; } -} -const canvas = document.getElementById("draw"); -const viewport = document.getElementById("viewport"); -const ctx = canvas.getContext("2d"); - -const gridSpacing = 40; -const dotRadius = 2; -let zoom = 1; -let offsetX = 0, - offsetY = 0; - -let nodes = [ - new VNode(100, 100, "A", [{ type: "output", name: "outA" }]), - new VNode(400, 200, "B", [{ type: "input", name: "inB" }]), - new VNode(200, 320, "C", [ - { type: "input", name: "inC" }, - { type: "output", name: "outC" }, - { type: "output", name: "outC2" }, - ]), -]; - -let connections = []; -let mouse = { x: 0, y: 0 }; -let draggingNode = null, - dragOffsetX, - dragOffsetY; -let connectingFrom = null; // { node, outIdx } -let isPanning = false; -let panStart = { x: 0, y: 0 }, - panOrigin = { x: 0, y: 0 }; - -function fitNodeSizeToGrid(node) { - node.width = Math.ceil(node.width / gridSpacing) * gridSpacing; - node.height = Math.ceil(node.height / gridSpacing) * gridSpacing; + draw(ctx /*, viewport - not required here but kept for parity */) { + const from = this.fromNode.getOutputCoords(this.fromOutput); + const to = this.toNode.getInputCoords(this.toInput); + ctx.strokeStyle = this.color; + ctx.lineWidth = this.width; + ctx.beginPath(); + ctx.moveTo(from.x, from.y); + ctx.bezierCurveTo(from.x + 40, from.y, to.x - 40, to.y, to.x, to.y); + ctx.stroke(); + } } -function resize() { - const rect = viewport.getBoundingClientRect(); - canvas.width = rect.width; - canvas.height = rect.height; - draw(); -} -window.addEventListener("resize", resize); +class Viewport { + constructor(containerElement, options = {}) { + if (!(containerElement instanceof HTMLElement)) { + throw new Error("Viewport constructor requires a DOM container element"); + } + // options and defaults + this.gridSpacing = options.gridSpacing || 40; + this.dotRadius = options.dotRadius || 2; + this.minZoom = options.minZoom || 0.4; + this.maxZoom = options.maxZoom || 2.5; -function toEditor(x, y) { - return { - x: (x - offsetX) / zoom, - y: (y - offsetY) / zoom, - }; -} + // internal state + this.zoom = options.zoom || 1; + this.offsetX = options.offsetX || 0; + this.offsetY = options.offsetY || 0; -function draw() { - ctx.setTransform(1, 0, 0, 1, 0, 0); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.setTransform(zoom, 0, 0, zoom, offsetX, offsetY); - - // Grid - ctx.fillStyle = "#3a3c40"; - const [w, h] = [ - (canvas.width - offsetX) / zoom, - (canvas.height - offsetY) / zoom, - ]; - const startX = Math.floor(-offsetX / zoom / gridSpacing) * gridSpacing; - const startY = Math.floor(-offsetY / zoom / gridSpacing) * gridSpacing; - for (let x = startX; x < w + gridSpacing; x += gridSpacing) - for (let y = startY; y < h + gridSpacing; y += gridSpacing) { - ctx.beginPath(); - ctx.arc(x, y, dotRadius, 0, 2 * Math.PI); - ctx.fill(); - } + this.nodes = []; + this.connections = []; - // Connections - for (let c of connections) { - const from = c.fromNode.getOutputCoords(c.fromOutput); - const to = c.toNode.getInputCoords(c.toInput); - ctx.strokeStyle = "#fb0"; - ctx.lineWidth = 3; - ctx.beginPath(); - ctx.moveTo(from.x, from.y); - ctx.bezierCurveTo(from.x + 40, from.y, to.x - 40, to.y, to.x, to.y); - ctx.stroke(); + this.mouse = { x: 0, y: 0 }; + this.draggingNode = null; + this.dragOffsetX = 0; + this.dragOffsetY = 0; + this.connectingFrom = null; // { node, outIdx } + this.isPanning = false; + this.panStart = { x: 0, y: 0 }; + this.panOrigin = { x: 0, y: 0 }; + + // create canvas and append to container + this.container = containerElement; + this.canvas = document.createElement("canvas"); + this.canvas.style.width = "100%"; + this.canvas.style.height = "100%"; + this.canvas.style.display = "block"; + this.container.appendChild(this.canvas); + this.ctx = this.canvas.getContext("2d"); + + // bind methods + this._onResize = this._onResize.bind(this); + this._onMouseDown = this._onMouseDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this._onWheel = this._onWheel.bind(this); + + // event listeners + window.addEventListener("resize", this._onResize); + this.canvas.addEventListener("mousedown", this._onMouseDown); + this.canvas.addEventListener("mousemove", this._onMouseMove); + this.canvas.addEventListener("mouseup", this._onMouseUp); + this.canvas.addEventListener("wheel", this._onWheel, { passive: false }); + + // initial sizing and draw + this._onResize(); } - // Live connect preview - if (connectingFrom) { - const from = connectingFrom.node.getOutputCoords(connectingFrom.outIdx); - ctx.strokeStyle = "#fb0"; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(from.x, from.y); - ctx.bezierCurveTo( - from.x + 40, - from.y, - mouse.x - 40, - mouse.y, - mouse.x, - mouse.y, - ); - ctx.stroke(); + + // convenience factory + createNode(x, y, label, params = []) { + const node = new VNode(x, y, label, params); + this.addNode(node); + return node; } - // Nodes - for (let n of nodes) { - fitNodeSizeToGrid(n); + addNode(node) { + this.nodes.push(node); + this.draw(); + } - ctx.fillStyle = "#374565"; - ctx.strokeStyle = "#58a"; - ctx.lineWidth = 2; - ctx.fillRect(n.x, n.y, n.width, n.height); - ctx.strokeRect(n.x, n.y, n.width, n.height); - ctx.fillStyle = "#fff"; - ctx.font = "16px sans-serif"; - ctx.fillText(n.label, n.x + 10, n.y + 22); - - // Inputs (left) - n.inputs.forEach((input, i) => { - const p = n.getInputCoords(i); - ctx.beginPath(); - ctx.arc(p.x, p.y, 12, 0, 2 * Math.PI); - ctx.fillStyle = "#48e"; - ctx.fill(); - ctx.stroke(); - ctx.fillStyle = "#fff"; - ctx.fillText(input.name, p.x + 16, p.y + 6); - }); + addConnection(conn) { + this.connections.push(conn); + this.draw(); + } + + clear() { + this.nodes = []; + this.connections = []; + this.draw(); + } + + // convert canvas pixel coords to editor coordinates (considering transform) + toEditor(x, y) { + return { + x: (x - this.offsetX) / this.zoom, + y: (y - this.offsetY) / this.zoom, + }; + } + + // resizing helper + _onResize() { + const rect = this.container.getBoundingClientRect(); + this.canvas.width = rect.width; + this.canvas.height = rect.height; + this.draw(); + } + + // central draw method for the viewport (required) + draw() { + const ctx = this.ctx; + // reset transform & clear + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + // apply zoom + pan transform + ctx.setTransform(this.zoom, 0, 0, this.zoom, this.offsetX, this.offsetY); - // Outputs (right) - n.outputs.forEach((output, i) => { - const p = n.getOutputCoords(i); + // Grid dots + ctx.fillStyle = "#3a3c40"; + const w = (this.canvas.width - this.offsetX) / this.zoom; + const h = (this.canvas.height - this.offsetY) / this.zoom; + const startX = + Math.floor(-this.offsetX / this.zoom / this.gridSpacing) * + this.gridSpacing; + const startY = + Math.floor(-this.offsetY / this.zoom / this.gridSpacing) * + this.gridSpacing; + for (let x = startX; x < w + this.gridSpacing; x += this.gridSpacing) { + for (let y = startY; y < h + this.gridSpacing; y += this.gridSpacing) { + ctx.beginPath(); + ctx.arc(x, y, this.dotRadius, 0, 2 * Math.PI); + ctx.fill(); + } + } + + // Connections + for (let c of this.connections) { + c.draw(ctx, this); + } + + // Live connect preview + if (this.connectingFrom) { + const from = this.connectingFrom.node.getOutputCoords( + this.connectingFrom.outIdx, + ); + ctx.strokeStyle = "#fb0"; + ctx.lineWidth = 2; ctx.beginPath(); - ctx.arc(p.x, p.y, 12, 0, 2 * Math.PI); - ctx.fillStyle = "#fa3"; - ctx.fill(); + ctx.moveTo(from.x, from.y); + ctx.bezierCurveTo( + from.x + 40, + from.y, + this.mouse.x - 40, + this.mouse.y, + this.mouse.x, + this.mouse.y, + ); ctx.stroke(); - ctx.fillStyle = "#fff"; - ctx.fillText(output.name, p.x - 60, p.y + 6); - }); + } + + // VNodes + for (let n of this.nodes) { + n.draw(ctx, this); + } + + // reset transform for any overlay drawing in screen space if desired + ctx.setTransform(1, 0, 0, 1, 0, 0); } - ctx.setTransform(1, 0, 0, 1, 0, 0); -} + // -------- Input handlers ---------- + _onMouseDown(e) { + const p = this.toEditor(e.offsetX, e.offsetY); + this.mouse = p; -function add_listeners() { - viewport.addEventListener("mousedown", (e) => { - console.log("FIRED!"); - const p = toEditor(e.offsetX, e.offsetY); - mouse = p; // output hit first (for drag to input) - for (let node of nodes) { + for (let node of this.nodes) { let outIdx = node.outputHit(p.x, p.y); if (outIdx !== null) { - connectingFrom = { node, outIdx }; + this.connectingFrom = { node, outIdx }; + // prevent pan from starting when clicking a socket + e.preventDefault(); return; } } - // hit test nodes - for (let node of nodes) { + + // hit test nodes (dragging) + for (let node of this.nodes) { if (node.contains(p.x, p.y)) { - draggingNode = node; - dragOffsetX = p.x - node.x; - dragOffsetY = p.y - node.y; + this.draggingNode = node; + this.dragOffsetX = p.x - node.x; + this.dragOffsetY = p.y - node.y; return; } } - // Panning - isPanning = true; - panOrigin.x = offsetX; - panOrigin.y = offsetY; - panStart.x = e.offsetX; - panStart.y = e.offsetY; - }); - - viewport.addEventListener("mousemove", (e) => { - const p = toEditor(e.offsetX, e.offsetY); - mouse = p; - if (draggingNode) { - let newX = Math.round((p.x - dragOffsetX) / gridSpacing) * gridSpacing; - let newY = Math.round((p.y - dragOffsetY) / gridSpacing) * gridSpacing; - draggingNode.x = newX; - draggingNode.y = newY; - fitNodeSizeToGrid(draggingNode); - draw(); - } else if (isPanning) { - offsetX = panOrigin.x + (e.offsetX - panStart.x); - offsetY = panOrigin.y + (e.offsetY - panStart.y); - draw(); - } else if (connectingFrom) { - draw(); + + // start panning + this.isPanning = true; + this.panOrigin.x = this.offsetX; + this.panOrigin.y = this.offsetY; + this.panStart.x = e.offsetX; + this.panStart.y = e.offsetY; + } + + _onMouseMove(e) { + const p = this.toEditor(e.offsetX, e.offsetY); + this.mouse = p; + if (this.draggingNode) { + let newX = + Math.round((p.x - this.dragOffsetX) / this.gridSpacing) * + this.gridSpacing; + let newY = + Math.round((p.y - this.dragOffsetY) / this.gridSpacing) * + this.gridSpacing; + this.draggingNode.x = newX; + this.draggingNode.y = newY; + this.draggingNode.fitToGrid(this.gridSpacing); + this.draw(); + } else if (this.isPanning) { + this.offsetX = this.panOrigin.x + (e.offsetX - this.panStart.x); + this.offsetY = this.panOrigin.y + (e.offsetY - this.panStart.y); + this.draw(); + } else if (this.connectingFrom) { + this.draw(); } - }); - - viewport.addEventListener("mouseup", (e) => { - const p = toEditor(e.offsetX, e.offsetY); - if (draggingNode) { - draggingNode = null; - draw(); - } else if (connectingFrom) { - for (let node of nodes) { + } + + _onMouseUp(e) { + const p = this.toEditor(e.offsetX, e.offsetY); + if (this.draggingNode) { + this.draggingNode = null; + this.draw(); + } else if (this.connectingFrom) { + for (let node of this.nodes) { let inIdx = node.inputHit(p.x, p.y); - if (inIdx !== null && node !== connectingFrom.node) { - connections.push( + if (inIdx !== null && node !== this.connectingFrom.node) { + this.connections.push( new Connection( - connectingFrom.node, - connectingFrom.outIdx, + this.connectingFrom.node, + this.connectingFrom.outIdx, node, inIdx, ), @@ -267,34 +392,44 @@ function add_listeners() { break; } } - connectingFrom = null; - draw(); - } else if (isPanning) { - isPanning = false; - draw(); + this.connectingFrom = null; + this.draw(); + } else if (this.isPanning) { + this.isPanning = false; + this.draw(); } - }); + } - viewport.addEventListener("wheel", (e) => { + _onWheel(e) { let scale = 1 + (e.deltaY < 0 ? 0.1 : -0.1); let mx = e.offsetX, my = e.offsetY; - const before = toEditor(mx, my); - zoom = Math.max(0.4, Math.min(2.5, zoom * scale)); - const after = toEditor(mx, my); - offsetX += (after.x - before.x) * zoom; - offsetY += (after.y - before.y) * zoom; - draw(); + const before = this.toEditor(mx, my); + this.zoom = Math.max( + this.minZoom, + Math.min(this.maxZoom, this.zoom * scale), + ); + const after = this.toEditor(mx, my); + // Adjust offsets so that the point under the mouse stays stationary in editor space + this.offsetX += (after.x - before.x) * this.zoom; + this.offsetY += (after.y - before.y) * this.zoom; + this.draw(); e.preventDefault(); - }); -} + } -function run() { - console.log("RUNNING"); - add_listeners(); - resize(); - draw(); + // cleanup listeners when you want to destroy the viewport + destroy() { + window.removeEventListener("resize", this._onResize); + this.canvas.removeEventListener("mousedown", this._onMouseDown); + this.canvas.removeEventListener("mousemove", this._onMouseMove); + this.canvas.removeEventListener("mouseup", this._onMouseUp); + this.canvas.removeEventListener("wheel", this._onWheel); + if (this.canvas.parentElement) { + this.canvas.parentElement.removeChild(this.canvas); + } + } } -window.run_editor = run; -window.run_editor(); +window.Parameter = Parameter; +window.VNode = VNode; +window.Viewport = Viewport; diff --git a/packages/frontend/src/modules/pages/editor.rs b/packages/frontend/src/modules/pages/editor.rs index 030181b..c4bea67 100644 --- a/packages/frontend/src/modules/pages/editor.rs +++ b/packages/frontend/src/modules/pages/editor.rs @@ -7,11 +7,17 @@ use std::collections::HashMap; // %% main %% #[page] pub fn Editor() -> Element { - document::eval(r#"console.log("hello world"); window.run_editor(); window.run_hello();"#); + let future = use_resource(move || async move { + // You can create as many eval instances as you want + let mut eval = document::eval( + r#"const vp = new window.Viewport(document.getElementById("viewport")); +vp.addNode(new window.VNode(100, 100, "A", [new window.Parameter("output", "outA")]));"#, + ); + }); rsx! { main { section { id: "viewport", - canvas { id: "draw" } + // canvas { id: "draw" } document::Script { src: asset!("/assets/scripts/viewport.js"), defer: true } } } From 43c31d0f382638cdb48b92fc7c6c4053d5d52120 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 28 Nov 2025 19:48:59 +0100 Subject: [PATCH 12/17] updated search bar --- packages/frontend/assets/scripts/sidebar.js | 302 ++++++++++++++++++ packages/frontend/assets/style/entry.css | 5 +- packages/frontend/assets/style/items/node.css | 7 +- .../frontend/assets/style/items/search.css | 5 +- .../assets/style/items/search_result.css | 17 +- .../frontend/assets/style/pages/editor.css | 12 + .../src/modules/components/draggable.rs | 3 +- .../frontend/src/modules/components/node.rs | 9 +- .../frontend/src/modules/components/search.rs | 25 +- .../src/modules/components/search_result.rs | 30 +- packages/frontend/src/modules/pages/editor.rs | 5 +- 11 files changed, 349 insertions(+), 71 deletions(-) create mode 100644 packages/frontend/assets/scripts/sidebar.js diff --git a/packages/frontend/assets/scripts/sidebar.js b/packages/frontend/assets/scripts/sidebar.js new file mode 100644 index 0000000..c648a47 --- /dev/null +++ b/packages/frontend/assets/scripts/sidebar.js @@ -0,0 +1,302 @@ +export class Sidebar { + // containerEl: the sidebar container element (where the UI will be rendered). + // options: + // viewport: an existing Viewport instance (optional) + // viewportContainer: element to create a viewport in (optional; used if viewport not provided) + // createViewport: function(container) -> Viewport (optional; defaults to new Viewport(container)) + // nodeDefs: array of node definitions + constructor(containerEl, options = {}) { + if (!(containerEl instanceof HTMLElement)) { + throw new Error("Sidebar requires a DOM element as first argument"); + } + this.containerEl = containerEl; + this.nodeDefs = options.nodeDefs || []; + this._bound = new Map(); + + // Setup or use viewport + this.vp = null; + if (options.viewport instanceof Viewport) { + this.vp = options.viewport; + } else if (options.viewportContainer instanceof HTMLElement) { + const factory = + typeof options.createViewport === "function" + ? options.createViewport + : (c) => new Viewport(c, options.viewportOptions || {}); + this.vp = factory(options.viewportContainer); + } else { + // no viewport provided, create one in a sibling element if available + // try to find a child with class 'viewport-container' inside the same app parent + const defaultViewportContainer = document.querySelector( + ".viewport-container", + ); + if (defaultViewportContainer) { + const factory = + typeof options.createViewport === "function" + ? options.createViewport + : (c) => new Viewport(c, options.viewportOptions || {}); + this.vp = factory(defaultViewportContainer); + } + } + + // Build sidebar UI + this._render(); + + // initial list + this.renderList(); + + // attach drag/drop handlers to viewport container if viewport exists + if (this.vp && this.vp.container instanceof HTMLElement) { + this._addDropHandlers(this.vp.container); + } + + // expose for debugging + this.rootSidebarElement = this.rootEl; + } + + setNodeDefs(nodeDefs) { + this.nodeDefs = Array.isArray(nodeDefs) ? nodeDefs.slice() : []; + this.renderList(); + } + + addNodeDef(def) { + this.nodeDefs.push(def); + this.renderList(); + } + + // create the sidebar DOM structure + _render() { + // Clear container + this.containerEl.innerHTML = ""; + + // root + const root = document.createElement("div"); + root.className = "sidebar-root"; + root.style.display = "flex"; + root.style.flexDirection = "column"; + root.style.gap = "8px"; + this.containerEl.appendChild(root); + this.rootEl = root; + + // header with search + const header = document.createElement("div"); + header.className = "sidebar-header"; + header.style.display = "flex"; + header.style.alignItems = "center"; + header.style.gap = "8px"; + root.appendChild(header); + + const search = document.createElement("input"); + search.type = "search"; + search.placeholder = "Search nodes..."; + search.className = "sidebar-search"; + search.style.width = "100%"; + search.style.padding = "8px"; + search.style.borderRadius = "6px"; + search.style.border = "none"; + search.style.background = "rgba(0,0,0,0.15)"; + search.style.color = "#fff"; + header.appendChild(search); + this.searchInput = search; + + // list + const list = document.createElement("div"); + list.className = "node-list"; + list.style.overflow = "auto"; + list.style.padding = "4px 2px 12px 2px"; + list.style.flex = "1 1 auto"; + root.appendChild(list); + this.listEl = list; + + // listeners + const onSearch = (e) => this.renderList(e.target.value || ""); + this.searchInput.addEventListener("input", onSearch); + this._bound.set("search-input", onSearch); + } + + // create a DOM node item for a definition + _makeNodeItem(def) { + const el = document.createElement("div"); + el.className = "node-item"; + el.style.display = "block"; + el.style.cursor = "grab"; + el.style.userSelect = "none"; + el.style.padding = "10px"; + el.style.borderRadius = "8px"; + el.style.marginBottom = "8px"; + el.style.background = "linear-gradient(180deg,#182034,#1f2940)"; + el.setAttribute("draggable", "true"); + el.dataset.def = JSON.stringify(def); + + const h = document.createElement("h4"); + h.textContent = def.label || def.id || "Node"; + h.style.margin = "0 0 6px 0"; + h.style.fontSize = "15px"; + h.style.color = "#fff"; + el.appendChild(h); + + if (def.description) { + const p = document.createElement("p"); + p.textContent = def.description; + p.style.margin = "0"; + p.style.fontSize = "12px"; + p.style.color = "#9aa3b2"; + el.appendChild(p); + } + + if (Array.isArray(def.params) && def.params.length) { + const meta = document.createElement("div"); + meta.style.marginTop = "8px"; + meta.style.display = "flex"; + meta.style.gap = "6px"; + meta.style.flexWrap = "wrap"; + def.params.forEach((pp) => { + const pill = document.createElement("div"); + pill.textContent = (pp.type === "input" ? "in: " : "out: ") + pp.name; + pill.style.fontSize = "11px"; + pill.style.padding = "4px 8px"; + pill.style.borderRadius = "999px"; + pill.style.background = "rgba(255,255,255,0.03)"; + pill.style.color = "#cfe5ff"; + meta.appendChild(pill); + }); + el.appendChild(meta); + } + + // drag handlers + el.addEventListener("dragstart", (ev) => { + try { + ev.dataTransfer.setData("application/json", JSON.stringify(def)); + } catch (err) { + // ignore + } + // optional drag image + if (ev.dataTransfer.setDragImage) { + const img = document.createElement("canvas"); + img.width = 160; + img.height = 40; + const ctx = img.getContext("2d"); + ctx.fillStyle = "#253042"; + ctx.fillRect(0, 0, img.width, img.height); + ctx.fillStyle = "#fff"; + ctx.font = "14px sans-serif"; + ctx.fillText(def.label || def.id || "Node", 10, 26); + ev.dataTransfer.setDragImage(img, 10, 10); + } + }); + + return el; + } + + // render node list filtered by the search query + renderList(filter = "") { + this.listEl.innerHTML = ""; + const q = String(filter || "") + .trim() + .toLowerCase(); + for (const def of this.nodeDefs) { + if ( + !q || + (def.label && def.label.toLowerCase().includes(q)) || + (def.description && def.description.toLowerCase().includes(q)) + ) { + this.listEl.appendChild(this._makeNodeItem(def)); + } + } + } + + // attach drop handlers to the given element (viewport container) + _addDropHandlers(dropTarget) { + if (!dropTarget || !(dropTarget instanceof HTMLElement)) return; + const onDragOver = (ev) => { + ev.preventDefault(); + }; + const onDrop = (ev) => { + ev.preventDefault(); + if (!this.vp) return; + try { + const raw = ev.dataTransfer.getData("application/json"); + if (!raw) return; + const def = JSON.parse(raw); + + // compute local canvas coordinates of drop + const rect = this.vp.canvas.getBoundingClientRect(); + const cx = ev.clientX - rect.left; + const cy = ev.clientY - rect.top; + + const pos = this.vp.toEditor(cx, cy); + + // create node using viewport.createNode if available, otherwise directly call createNode + if (typeof this.vp.createNode === "function") { + this.vp.createNode( + pos.x, + pos.y, + def.label || def.id, + def.params || [], + ); + } else { + // fallback: instantiate Node & add + const NodeClass = this.vp.Node || null; + if (typeof this.vp.addNode === "function") { + // attempt to create a node-like object + this.vp.addNode({ + x: pos.x, + y: pos.y, + label: def.label || def.id, + params: def.params || [], + }); + } + } + + // emit a DOM CustomEvent for consumers + const evDetail = { def, pos }; + const dropped = new CustomEvent("sidebar-node-drop", { + detail: evDetail, + }); + dropTarget.dispatchEvent(dropped); + } catch (err) { + // ignore malformed drops + console.error("Sidebar drop error", err); + } + }; + + dropTarget.addEventListener("dragover", onDragOver); + dropTarget.addEventListener("drop", onDrop); + + this._bound.set("drop-target", { el: dropTarget, onDragOver, onDrop }); + } + + // If you created the viewport later or want to rebind drop handlers: + attachToViewport(viewportInstance) { + if (!(viewportInstance instanceof Viewport)) return; + // remove previous drop handlers if any + const prev = this._bound.get("drop-target"); + if (prev && prev.el) { + prev.el.removeEventListener("dragover", prev.onDragOver); + prev.el.removeEventListener("drop", prev.onDrop); + } + this.vp = viewportInstance; + this._addDropHandlers(this.vp.container); + } + + // remove listeners and DOM created by Sidebar + destroy() { + // remove search listener + const onSearch = this._bound.get("search-input"); + if (onSearch) this.searchInput.removeEventListener("input", onSearch); + + // remove drop listener + const prev = this._bound.get("drop-target"); + if (prev && prev.el) { + prev.el.removeEventListener("dragover", prev.onDragOver); + prev.el.removeEventListener("drop", prev.onDrop); + } + + // clear DOM + if (this.rootEl && this.rootEl.parentElement) { + this.rootEl.parentElement.removeChild(this.rootEl); + } + + // do not destroy viewport here (consumer may still use it) + this._bound.clear(); + } +} diff --git a/packages/frontend/assets/style/entry.css b/packages/frontend/assets/style/entry.css index 7ebdb16..5c3c5a3 100644 --- a/packages/frontend/assets/style/entry.css +++ b/packages/frontend/assets/style/entry.css @@ -6,8 +6,9 @@ --color-bg: var(--color-1), 5%; --color-text-fg: var(--color-1), 90%; - --color-box-fg: var(--color-1), 90%; - --color-box-bg: var(--color-1), 20%; + --color-box: var(--color-1); + --color-box-fg: var(--color-box), 90%; + --color-box-bg: var(--color-box), 20%; --color-button-fg: var(--color-box-fg); --color-button-bg: var(--color-box-bg); --color-icon-bg: transparent; diff --git a/packages/frontend/assets/style/items/node.css b/packages/frontend/assets/style/items/node.css index 8e34989..8d2cdbe 100644 --- a/packages/frontend/assets/style/items/node.css +++ b/packages/frontend/assets/style/items/node.css @@ -1,7 +1,4 @@ .Node { - border-width: var(--border-width); - background-color: rgba(var(--accent-color), 0.5); - border-color: rgb(var(--border-color)); - border-radius: var(--border-radius); - border-style: solid; + background-color: hsl(var(--color-box-bg)); + border-radius: 1rem; } diff --git a/packages/frontend/assets/style/items/search.css b/packages/frontend/assets/style/items/search.css index c7b2319..508df25 100644 --- a/packages/frontend/assets/style/items/search.css +++ b/packages/frontend/assets/style/items/search.css @@ -4,7 +4,6 @@ flex-direction: column; justify-content: stretch; align-items: center; - background-color: rgba(var(--background-highlight-color), 0.35); } .Search main { @@ -13,6 +12,7 @@ justify-content: stretch; align-items: center; overflow-y: scroll; + padding: 1rem 0; } .Search header { @@ -21,4 +21,7 @@ } .Search .results { + display: flex; + flex-direction: column; + gap: 1rem; } diff --git a/packages/frontend/assets/style/items/search_result.css b/packages/frontend/assets/style/items/search_result.css index c3728da..0c0c159 100644 --- a/packages/frontend/assets/style/items/search_result.css +++ b/packages/frontend/assets/style/items/search_result.css @@ -1,17 +1,6 @@ .SearchResult { + background-color: hsl(var(--color-box-bg)); flex-grow: 1; - border-width: var(--border-width); - background-color: rgba(var(--accent-color), 0.5); - border-color: rgb(var(--border-color)); - border-radius: var(--border-radius); - border-style: solid; - padding: 1.5rem; - margin: 1rem; -} - -.Draggable:has(.SearchResult) { - display: flex; - align-items: center; - justify-content: center; - width: 100%; + padding: 1rem; + border-radius: 1rem; } diff --git a/packages/frontend/assets/style/pages/editor.css b/packages/frontend/assets/style/pages/editor.css index ea51af3..e923ef2 100644 --- a/packages/frontend/assets/style/pages/editor.css +++ b/packages/frontend/assets/style/pages/editor.css @@ -7,3 +7,15 @@ #paint { flex-grow: 1; } + +.Editor { + display: flex; + flex-direction: row; + overflow: hidden; +} + +aside { + background-color: hsl(var(--color-1), 10%); + backdrop-filter: blur(1rem); + padding: 1rem; +} diff --git a/packages/frontend/src/modules/components/draggable.rs b/packages/frontend/src/modules/components/draggable.rs index 1bafe85..0a4d569 100644 --- a/packages/frontend/src/modules/components/draggable.rs +++ b/packages/frontend/src/modules/components/draggable.rs @@ -116,7 +116,6 @@ pub fn Draggable( rsx! { div { - class: "Draggable", top: 0, left: 0, height: height_handle, @@ -131,7 +130,7 @@ pub fn Draggable( onmounted: mounted, onmouseenter: mouseenter, onmouseleave: mouseleave, - { children } + {children} } } } diff --git a/packages/frontend/src/modules/components/node.rs b/packages/frontend/src/modules/components/node.rs index 18e4298..449cae3 100644 --- a/packages/frontend/src/modules/components/node.rs +++ b/packages/frontend/src/modules/components/node.rs @@ -82,7 +82,9 @@ pub fn Node(intern: InternNode) -> Element { let rendered_params = intern .runtime_params .iter() - .map(|intern| rsx! { RuntimeParam { intern: intern.clone() } }); + .map(|intern| rsx! { + RuntimeParam { intern: intern.clone() } + }); rsx! { body { @@ -98,17 +100,16 @@ pub fn Node(intern: InternNode) -> Element { user_select: "none", onmousedown: mousedown, onmouseover: move |_| { intern.cursor.set("grab".into()) }, - h1 { { } } + h1 { {} } } main { display: "flex", flex_direction: "column", justify_content: "space-evenly", align_items: "center", - { rendered_params } + {rendered_params} } footer { - } } } diff --git a/packages/frontend/src/modules/components/search.rs b/packages/frontend/src/modules/components/search.rs index 11925aa..064d440 100644 --- a/packages/frontend/src/modules/components/search.rs +++ b/packages/frontend/src/modules/components/search.rs @@ -7,7 +7,7 @@ use chrono::Utc; use simple_ai_backend::modules::utils::node::NodeBuilder; // %% main %% -#[component] +#[item] pub fn Search(#[props(extends = GlobalAttributes)] attributes: Vec) -> Element { let mut intern_search_results = use_signal(Vec::::new); let mut search_results = use_signal(Container::new); @@ -48,25 +48,18 @@ pub fn Search(#[props(extends = GlobalAttributes)] attributes: Vec) - ); rsx! { - article { - class: "Search", - ..attributes, + article { class: "Search", ..attributes, header { - input { - oninput: input, - type: "search", - placeholder: "search" - } - // nav { - // button { "your nodes" } - // button { "installed nodes" } - // button { "profiles" } - // } + input { oninput: input, r#type: "search", placeholder: "search" } + // nav { + // button { "your nodes" } + // button { "installed nodes" } + // button { "profiles" } + // } } main { div { class: "spacer" } - section { - class: "results", + section { class: "results", 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 index d74a356..62b05b5 100644 --- a/packages/frontend/src/modules/components/search_result.rs +++ b/packages/frontend/src/modules/components/search_result.rs @@ -19,7 +19,7 @@ impl From for InternSearchResult { } } -#[component] +#[item] pub fn SearchResult(intern: InternSearchResult) -> Element { let draggingend = move |v: PageVector| { let mut node = intern.node.cloned(); @@ -28,31 +28,11 @@ pub fn SearchResult(intern: InternSearchResult) -> Element { }; 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}" - } + article { + h3 { + span { id: "name", "{intern.node.cloned().name}" } } + p { id: "description", "{intern.node.cloned().description}" } } } } diff --git a/packages/frontend/src/modules/pages/editor.rs b/packages/frontend/src/modules/pages/editor.rs index c4bea67..8da1283 100644 --- a/packages/frontend/src/modules/pages/editor.rs +++ b/packages/frontend/src/modules/pages/editor.rs @@ -2,6 +2,7 @@ // %% includes %% use super::utils::*; +use crate::modules::components::search::*; use std::collections::HashMap; // %% main %% @@ -17,9 +18,9 @@ vp.addNode(new window.VNode(100, 100, "A", [new window.Parameter("output", "outA rsx! { main { section { id: "viewport", - // canvas { id: "draw" } - document::Script { src: asset!("/assets/scripts/viewport.js"), defer: true } + document::Script { src: asset!("/assets/scripts/viewport.js") } } + aside { Search {} } } } } From d9343a346acf55ee6613301b596dcc5f1445441f Mon Sep 17 00:00:00 2001 From: = Date: Tue, 2 Dec 2025 17:46:12 +0100 Subject: [PATCH 13/17] onnx viewport working so far --- .../frontend/assets/scripts/onnx-drag-in.js | 69 ++++ .../scripts/{viewport.js => onnx-viewport.js} | 227 +++++++++++-- packages/frontend/assets/scripts/sidebar.js | 302 ------------------ packages/frontend/assets/style/entry.css | 16 + .../frontend/assets/style/items/search.css | 20 +- .../assets/style/items/search_result.css | 2 +- .../frontend/assets/style/pages/editor.css | 2 - packages/frontend/assets/style/pages/new.css | 29 +- .../frontend/assets/style/pages/projects.css | 2 + .../frontend/src/modules/components/search.rs | 47 ++- packages/frontend/src/modules/pages/editor.rs | 27 +- .../frontend/src/modules/pages/projects.rs | 1 + packages/macros/src/formifiable.rs | 24 +- 13 files changed, 369 insertions(+), 399 deletions(-) create mode 100644 packages/frontend/assets/scripts/onnx-drag-in.js rename packages/frontend/assets/scripts/{viewport.js => onnx-viewport.js} (65%) delete mode 100644 packages/frontend/assets/scripts/sidebar.js diff --git a/packages/frontend/assets/scripts/onnx-drag-in.js b/packages/frontend/assets/scripts/onnx-drag-in.js new file mode 100644 index 0000000..514a306 --- /dev/null +++ b/packages/frontend/assets/scripts/onnx-drag-in.js @@ -0,0 +1,69 @@ +// A script that is applied to the OnnxDragIn with a serach bar input and a container of nodes. +// It offers search capabilities and draggable Nodes with can be added to the OnnxViewport. +// This only works with when the window.activeOnnxViewport is set. +// This approach offers better readablily as opposed to all java script. +// Also this could be replaced eventually with a native rendering solution from dixous +class OnnxDragIn { + constructor(rootelm, search, nodeContainer) { + if (!(rootelm instanceof HTMLElement)) { + throw new Error( + "Yout first argument is no valid DOM element. Please provide the drag in element itself.", + ); + } + if (!(search instanceof HTMLElement)) { + throw new Error( + "Your second argument is no valid DOM element. Please provide a search type input.", + ); + } + if (!(nodeContainer instanceof HTMLElement)) { + throw new Error( + "Your third argument is no valid DOM element. Please provide the container of the nodes.", + ); + } + this.rootelm = rootelm; + this.search = search; + this.draggingChild = null; + this.nodeContainer = nodeContainer; + // this.nodeContainer.style.display = "relative"; + this.nodes = Array.from(this.nodeContainer.children); + this._ondrag = this._ondrag.bind(this); + this._ondrop = this._ondrop.bind(this); + this.start_drag = null; + this._attachListeners(); + } + _ondrop(e) { + this.draggingChild.style.left = "initial"; + this.draggingChild.style.top = "initial"; + this.draggingChild.style.position = "initial"; + window.activeOnnxViewport.addNodeFromId(this.draggingChild.id, { + position: { x: e.x, y: e.y }, + }); + this.draggingChild = null; + } + _ondrag(e) { + let rect = this.draggingChild.getBoundingClientRect(); + this.draggingChild.style.left = `${e.x - rect.width / 2}px`; + this.draggingChild.style.top = `${e.y - rect.height / 2}px`; + } + _attachListeners() { + this.nodes.forEach((child) => { + console.log("hello"); + child.addEventListener("mousedown", (e) => { + this.draggingChild = child; + // let container_rect = this.nodeContainer.getBoundingClientRect(); + // let node_rect = this.draggingChild.getBoundingClientRect(); + // this.start_drag = { x: e.x, y: e.y }; + this.draggingChild.style.position = "fixed"; + window.addEventListener("mousemove", this._ondrag); + }); + }); + window.addEventListener("mouseup", (e) => { + if (this.draggingChild) { + window.removeEventListener("mousemove", this._ondrag); + this._ondrop(e); + } + }); + } +} + +window.OnnxDragIn = OnnxDragIn; diff --git a/packages/frontend/assets/scripts/viewport.js b/packages/frontend/assets/scripts/onnx-viewport.js similarity index 65% rename from packages/frontend/assets/scripts/viewport.js rename to packages/frontend/assets/scripts/onnx-viewport.js index 0ef4a43..1fcea00 100644 --- a/packages/frontend/assets/scripts/viewport.js +++ b/packages/frontend/assets/scripts/onnx-viewport.js @@ -1,16 +1,64 @@ -// A reusable, instantiable node-editor viewport module. -// Usage: -// import { Viewport, VNode, Parameter, Connection } from './viewport.js'; -// const vp = new Viewport(document.getElementById('editor')); -// vp.addNode(new VNode(100,100,'A',[new Parameter('output','outA')])); -// ... etc. +/** + * A reusable, instantiable node-editor viewport module. + * Usage: + * import { Viewport, VNode, Parameter, Connection } from './viewport.js'; + * const vp = new Viewport(document.getElementById('editor')); + * vp.addNode(new VNode(100,100,'A',[new Parameter('output','outA')])); + * ... etc. + **/ + +function drawRoundedRect(ctx, x, y, width, height, radius = 6, options = {}) { + // radius can be a number or an object {tl, tr, br, bl} + const r = + typeof radius === "number" + ? { tl: radius, tr: radius, br: radius, bl: radius } + : Object.assign({ tl: 0, tr: 0, br: 0, bl: 0 }, radius); + + // clamp radii to not exceed half width/height + const maxR = Math.min(width, height) / 2; + r.tl = Math.min(r.tl, maxR); + r.tr = Math.min(r.tr, maxR); + r.br = Math.min(r.br, maxR); + r.bl = Math.min(r.bl, maxR); + + const { + fill = true, + stroke = false, + fillStyle, + strokeStyle, + lineWidth = 1, + } = options; + + ctx.beginPath(); + ctx.moveTo(x + r.tl, y); + ctx.lineTo(x + width - r.tr, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + r.tr); + ctx.lineTo(x + width, y + height - r.br); + ctx.quadraticCurveTo(x + width, y + height, x + width - r.br, y + height); + ctx.lineTo(x + r.bl, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - r.bl); + ctx.lineTo(x, y + r.tl); + ctx.quadraticCurveTo(x, y, x + r.tl, y); + ctx.closePath(); + + if (fill) { + if (fillStyle !== undefined) ctx.fillStyle = fillStyle; + ctx.fill(); + } + + if (stroke) { + if (strokeStyle !== undefined) ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.stroke(); + } +} class Parameter { constructor(type = "input", name = "") { this.type = type; // "input" or "output" this.name = name; // visual defaults (can be overridden externally) - this.radius = 12; + this.radius = 10; this.fillInput = "#48e"; this.fillOutput = "#fa3"; this.textColor = "#fff"; @@ -22,7 +70,7 @@ class Parameter { ctx.arc(pos.x, pos.y, this.radius, 0, 2 * Math.PI); ctx.fillStyle = isInput ? this.fillInput : this.fillOutput; ctx.fill(); - ctx.stroke(); + // ctx.stroke(); ctx.fillStyle = this.textColor; // Labels are drawn to the right for inputs, to the left for outputs @@ -36,8 +84,6 @@ class Parameter { class VNode { constructor(x = 0, y = 0, label = "Node", params = []) { - this.x = x; - this.y = y; this.label = label; // params is array of Parameter instances or plain objects {type,name} this.params = params.map((p) => @@ -47,32 +93,34 @@ class VNode { this.outputs = this.params.filter((p) => p.type === "output"); // visual defaults - this.paramSpacing = 32; + this.paramSpacing = 50; this.width = 160; this.height = Math.max( 48, 32 + Math.max(this.inputs.length, this.outputs.length) * this.paramSpacing, ); - - this.fillStyle = "#374565"; + this.x = x - this.width / 2; + this.y = y - this.height / 2; + this.fillStyle = "#193c4d"; this.strokeStyle = "#58a"; this.titleColor = "#fff"; this.titleFont = "16px sans-serif"; + this.heading_space = 60; } // compute input socket coordinates getInputCoords(i) { return { x: this.x, - y: this.y + 32 + i * this.paramSpacing, + y: this.y + this.heading_space + i * this.paramSpacing, }; } // compute output socket coordinates getOutputCoords(i) { return { x: this.x + this.width, - y: this.y + 32 + i * this.paramSpacing, + y: this.y + this.heading_space + i * this.paramSpacing, }; } @@ -113,10 +161,12 @@ class VNode { this.fitToGrid(viewport.gridSpacing); ctx.fillStyle = this.fillStyle; - ctx.strokeStyle = this.strokeStyle; - ctx.lineWidth = 2; - ctx.fillRect(this.x, this.y, this.width, this.height); - ctx.strokeRect(this.x, this.y, this.width, this.height); + // ctx.strokeStyle = this.strokeStyle; + // ctx.lineWidth = 2; + // drawLiquidGlassRect(ctx, this.x, this.y, this.width, this.height); + drawRoundedRect(ctx, this.x, this.y, this.width, this.height, 15); + // ctx.fillRect(this.x, this.y, this.width, this.height); + // ctx.strokeRect(this.x, this.y, this.width, this.height); ctx.fillStyle = this.titleColor; ctx.font = this.titleFont; @@ -148,6 +198,27 @@ class Connection { this.width = 3; } + hit(px, py) { + // Simple hit test: closest approach to Bezier curve + // We'll use a simple linear approach for now for demo (improve if accuracy needed) + const from = this.fromNode.getOutputCoords(this.fromOutput); + const to = this.toNode.getInputCoords(this.toInput); + // test a few points along the curve + for (let t = 0; t <= 1; t += 0.05) { + let x = + Math.pow(1 - t, 3) * from.x + + 3 * Math.pow(1 - t, 2) * t * (from.x + 40) + + 3 * (1 - t) * Math.pow(t, 2) * (to.x - 40) + + Math.pow(t, 3) * to.x; + let y = + Math.pow(1 - t, 3) * from.y + + 3 * Math.pow(1 - t, 2) * t * from.y + + 3 * (1 - t) * Math.pow(t, 2) * to.y + + Math.pow(t, 3) * to.y; + if (Math.hypot(px - x, py - y) < 10) return true; + } + return false; + } draw(ctx /*, viewport - not required here but kept for parity */) { const from = this.fromNode.getOutputCoords(this.fromOutput); const to = this.toNode.getInputCoords(this.toInput); @@ -197,12 +268,16 @@ class Viewport { this.container.appendChild(this.canvas); this.ctx = this.canvas.getContext("2d"); + this.selectedNode = null; + this.selectedConnection = null; + // bind methods this._onResize = this._onResize.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseMove = this._onMouseMove.bind(this); this._onMouseUp = this._onMouseUp.bind(this); this._onWheel = this._onWheel.bind(this); + this._onKeyDown = this._onKeyDown.bind(this); // event listeners window.addEventListener("resize", this._onResize); @@ -210,6 +285,7 @@ class Viewport { this.canvas.addEventListener("mousemove", this._onMouseMove); this.canvas.addEventListener("mouseup", this._onMouseUp); this.canvas.addEventListener("wheel", this._onWheel, { passive: false }); + document.addEventListener("keydown", this._onKeyDown); // initial sizing and draw this._onResize(); @@ -227,6 +303,22 @@ class Viewport { this.draw(); } + addNodeFromId(id, options = {}) { + let rect = this.container.getBoundingClientRect(); + const { + position = { x: rect.width / 2 + rect.x, y: rect.height / 2 + rect.y }, + } = options; + + let { x, y } = this.toEditor(position.x - rect.x, position.y - rect.y); + + this.addNode( + new window.VNode(x, y, "SampleNODE", [ + new window.Parameter("input", "inA"), + new window.Parameter("output", "outA"), + ]), + ); + } + addConnection(conn) { this.connections.push(conn); this.draw(); @@ -281,11 +373,6 @@ class Viewport { } } - // Connections - for (let c of this.connections) { - c.draw(ctx, this); - } - // Live connect preview if (this.connectingFrom) { const from = this.connectingFrom.node.getOutputCoords( @@ -306,9 +393,42 @@ class Viewport { ctx.stroke(); } + // Connections + for (let c of this.connections) { + if (c === this.selectedConnection) { + // Highlight selected connection + this.ctx.save(); + this.ctx.shadowColor = "#fb0"; + this.ctx.shadowBlur = 8; + c.draw(this.ctx, this); + this.ctx.restore(); + // Optional: thicker + this.ctx.save(); + this.ctx.strokeStyle = "#f00"; + this.ctx.lineWidth = 7; + c.draw(this.ctx, this); + this.ctx.restore(); + } else { + c.draw(this.ctx, this); + } + } + // VNodes for (let n of this.nodes) { - n.draw(ctx, this); + n.draw(this.ctx, this); + if (n === this.selectedNode) { + // Highlight selected node + this.ctx.save(); + // this.ctx.strokeStyle = "#fb0"; + // this.ctx.lineWidth = 7; + + this.ctx.strokeStyle = "#fb0"; + drawRoundedRect(this.ctx, n.x, n.y, n.width, n.height, 19, { + stroke: true, + fill: false, + }); + this.ctx.restore(); + } } // reset transform for any overlay drawing in screen space if desired @@ -325,18 +445,33 @@ class Viewport { let outIdx = node.outputHit(p.x, p.y); if (outIdx !== null) { this.connectingFrom = { node, outIdx }; - // prevent pan from starting when clicking a socket e.preventDefault(); + this.selectedNode = null; + this.selectedConnection = null; + this.draw(); return; } } - // hit test nodes (dragging) + // check connections first for hit + for (let i = 0; i < this.connections.length; ++i) { + if (this.connections[i].hit(p.x, p.y)) { + this.selectedConnection = this.connections[i]; + this.selectedNode = null; + this.draw(); + return; + } + } + + // hit test nodes (dragging/select) for (let node of this.nodes) { if (node.contains(p.x, p.y)) { this.draggingNode = node; this.dragOffsetX = p.x - node.x; this.dragOffsetY = p.y - node.y; + this.selectedNode = node; + this.selectedConnection = null; + this.draw(); return; } } @@ -347,6 +482,9 @@ class Viewport { this.panOrigin.y = this.offsetY; this.panStart.x = e.offsetX; this.panStart.y = e.offsetY; + this.selectedNode = null; + this.selectedConnection = null; + this.draw(); } _onMouseMove(e) { @@ -417,13 +555,46 @@ class Viewport { e.preventDefault(); } - // cleanup listeners when you want to destroy the viewport + _onKeyDown(e) { + if (e.key === "Delete" || e.key === "Backspace") { + let removed = false; + if (this.selectedNode) { + // Remove node and its connections + const idx = this.nodes.indexOf(this.selectedNode); + if (idx !== -1) { + this.nodes.splice(idx, 1); + // Remove connections attached to this node + this.connections = this.connections.filter( + (c) => + c.fromNode !== this.selectedNode && + c.toNode !== this.selectedNode, + ); + removed = true; + } + this.selectedNode = null; + } + if (this.selectedConnection) { + const idx = this.connections.indexOf(this.selectedConnection); + if (idx !== -1) { + this.connections.splice(idx, 1); + removed = true; + } + this.selectedConnection = null; + } + if (removed) { + this.draw(); + e.preventDefault(); + } + } + } + destroy() { window.removeEventListener("resize", this._onResize); this.canvas.removeEventListener("mousedown", this._onMouseDown); this.canvas.removeEventListener("mousemove", this._onMouseMove); this.canvas.removeEventListener("mouseup", this._onMouseUp); this.canvas.removeEventListener("wheel", this._onWheel); + document.removeEventListener("keydown", this._onKeyDown); if (this.canvas.parentElement) { this.canvas.parentElement.removeChild(this.canvas); } diff --git a/packages/frontend/assets/scripts/sidebar.js b/packages/frontend/assets/scripts/sidebar.js deleted file mode 100644 index c648a47..0000000 --- a/packages/frontend/assets/scripts/sidebar.js +++ /dev/null @@ -1,302 +0,0 @@ -export class Sidebar { - // containerEl: the sidebar container element (where the UI will be rendered). - // options: - // viewport: an existing Viewport instance (optional) - // viewportContainer: element to create a viewport in (optional; used if viewport not provided) - // createViewport: function(container) -> Viewport (optional; defaults to new Viewport(container)) - // nodeDefs: array of node definitions - constructor(containerEl, options = {}) { - if (!(containerEl instanceof HTMLElement)) { - throw new Error("Sidebar requires a DOM element as first argument"); - } - this.containerEl = containerEl; - this.nodeDefs = options.nodeDefs || []; - this._bound = new Map(); - - // Setup or use viewport - this.vp = null; - if (options.viewport instanceof Viewport) { - this.vp = options.viewport; - } else if (options.viewportContainer instanceof HTMLElement) { - const factory = - typeof options.createViewport === "function" - ? options.createViewport - : (c) => new Viewport(c, options.viewportOptions || {}); - this.vp = factory(options.viewportContainer); - } else { - // no viewport provided, create one in a sibling element if available - // try to find a child with class 'viewport-container' inside the same app parent - const defaultViewportContainer = document.querySelector( - ".viewport-container", - ); - if (defaultViewportContainer) { - const factory = - typeof options.createViewport === "function" - ? options.createViewport - : (c) => new Viewport(c, options.viewportOptions || {}); - this.vp = factory(defaultViewportContainer); - } - } - - // Build sidebar UI - this._render(); - - // initial list - this.renderList(); - - // attach drag/drop handlers to viewport container if viewport exists - if (this.vp && this.vp.container instanceof HTMLElement) { - this._addDropHandlers(this.vp.container); - } - - // expose for debugging - this.rootSidebarElement = this.rootEl; - } - - setNodeDefs(nodeDefs) { - this.nodeDefs = Array.isArray(nodeDefs) ? nodeDefs.slice() : []; - this.renderList(); - } - - addNodeDef(def) { - this.nodeDefs.push(def); - this.renderList(); - } - - // create the sidebar DOM structure - _render() { - // Clear container - this.containerEl.innerHTML = ""; - - // root - const root = document.createElement("div"); - root.className = "sidebar-root"; - root.style.display = "flex"; - root.style.flexDirection = "column"; - root.style.gap = "8px"; - this.containerEl.appendChild(root); - this.rootEl = root; - - // header with search - const header = document.createElement("div"); - header.className = "sidebar-header"; - header.style.display = "flex"; - header.style.alignItems = "center"; - header.style.gap = "8px"; - root.appendChild(header); - - const search = document.createElement("input"); - search.type = "search"; - search.placeholder = "Search nodes..."; - search.className = "sidebar-search"; - search.style.width = "100%"; - search.style.padding = "8px"; - search.style.borderRadius = "6px"; - search.style.border = "none"; - search.style.background = "rgba(0,0,0,0.15)"; - search.style.color = "#fff"; - header.appendChild(search); - this.searchInput = search; - - // list - const list = document.createElement("div"); - list.className = "node-list"; - list.style.overflow = "auto"; - list.style.padding = "4px 2px 12px 2px"; - list.style.flex = "1 1 auto"; - root.appendChild(list); - this.listEl = list; - - // listeners - const onSearch = (e) => this.renderList(e.target.value || ""); - this.searchInput.addEventListener("input", onSearch); - this._bound.set("search-input", onSearch); - } - - // create a DOM node item for a definition - _makeNodeItem(def) { - const el = document.createElement("div"); - el.className = "node-item"; - el.style.display = "block"; - el.style.cursor = "grab"; - el.style.userSelect = "none"; - el.style.padding = "10px"; - el.style.borderRadius = "8px"; - el.style.marginBottom = "8px"; - el.style.background = "linear-gradient(180deg,#182034,#1f2940)"; - el.setAttribute("draggable", "true"); - el.dataset.def = JSON.stringify(def); - - const h = document.createElement("h4"); - h.textContent = def.label || def.id || "Node"; - h.style.margin = "0 0 6px 0"; - h.style.fontSize = "15px"; - h.style.color = "#fff"; - el.appendChild(h); - - if (def.description) { - const p = document.createElement("p"); - p.textContent = def.description; - p.style.margin = "0"; - p.style.fontSize = "12px"; - p.style.color = "#9aa3b2"; - el.appendChild(p); - } - - if (Array.isArray(def.params) && def.params.length) { - const meta = document.createElement("div"); - meta.style.marginTop = "8px"; - meta.style.display = "flex"; - meta.style.gap = "6px"; - meta.style.flexWrap = "wrap"; - def.params.forEach((pp) => { - const pill = document.createElement("div"); - pill.textContent = (pp.type === "input" ? "in: " : "out: ") + pp.name; - pill.style.fontSize = "11px"; - pill.style.padding = "4px 8px"; - pill.style.borderRadius = "999px"; - pill.style.background = "rgba(255,255,255,0.03)"; - pill.style.color = "#cfe5ff"; - meta.appendChild(pill); - }); - el.appendChild(meta); - } - - // drag handlers - el.addEventListener("dragstart", (ev) => { - try { - ev.dataTransfer.setData("application/json", JSON.stringify(def)); - } catch (err) { - // ignore - } - // optional drag image - if (ev.dataTransfer.setDragImage) { - const img = document.createElement("canvas"); - img.width = 160; - img.height = 40; - const ctx = img.getContext("2d"); - ctx.fillStyle = "#253042"; - ctx.fillRect(0, 0, img.width, img.height); - ctx.fillStyle = "#fff"; - ctx.font = "14px sans-serif"; - ctx.fillText(def.label || def.id || "Node", 10, 26); - ev.dataTransfer.setDragImage(img, 10, 10); - } - }); - - return el; - } - - // render node list filtered by the search query - renderList(filter = "") { - this.listEl.innerHTML = ""; - const q = String(filter || "") - .trim() - .toLowerCase(); - for (const def of this.nodeDefs) { - if ( - !q || - (def.label && def.label.toLowerCase().includes(q)) || - (def.description && def.description.toLowerCase().includes(q)) - ) { - this.listEl.appendChild(this._makeNodeItem(def)); - } - } - } - - // attach drop handlers to the given element (viewport container) - _addDropHandlers(dropTarget) { - if (!dropTarget || !(dropTarget instanceof HTMLElement)) return; - const onDragOver = (ev) => { - ev.preventDefault(); - }; - const onDrop = (ev) => { - ev.preventDefault(); - if (!this.vp) return; - try { - const raw = ev.dataTransfer.getData("application/json"); - if (!raw) return; - const def = JSON.parse(raw); - - // compute local canvas coordinates of drop - const rect = this.vp.canvas.getBoundingClientRect(); - const cx = ev.clientX - rect.left; - const cy = ev.clientY - rect.top; - - const pos = this.vp.toEditor(cx, cy); - - // create node using viewport.createNode if available, otherwise directly call createNode - if (typeof this.vp.createNode === "function") { - this.vp.createNode( - pos.x, - pos.y, - def.label || def.id, - def.params || [], - ); - } else { - // fallback: instantiate Node & add - const NodeClass = this.vp.Node || null; - if (typeof this.vp.addNode === "function") { - // attempt to create a node-like object - this.vp.addNode({ - x: pos.x, - y: pos.y, - label: def.label || def.id, - params: def.params || [], - }); - } - } - - // emit a DOM CustomEvent for consumers - const evDetail = { def, pos }; - const dropped = new CustomEvent("sidebar-node-drop", { - detail: evDetail, - }); - dropTarget.dispatchEvent(dropped); - } catch (err) { - // ignore malformed drops - console.error("Sidebar drop error", err); - } - }; - - dropTarget.addEventListener("dragover", onDragOver); - dropTarget.addEventListener("drop", onDrop); - - this._bound.set("drop-target", { el: dropTarget, onDragOver, onDrop }); - } - - // If you created the viewport later or want to rebind drop handlers: - attachToViewport(viewportInstance) { - if (!(viewportInstance instanceof Viewport)) return; - // remove previous drop handlers if any - const prev = this._bound.get("drop-target"); - if (prev && prev.el) { - prev.el.removeEventListener("dragover", prev.onDragOver); - prev.el.removeEventListener("drop", prev.onDrop); - } - this.vp = viewportInstance; - this._addDropHandlers(this.vp.container); - } - - // remove listeners and DOM created by Sidebar - destroy() { - // remove search listener - const onSearch = this._bound.get("search-input"); - if (onSearch) this.searchInput.removeEventListener("input", onSearch); - - // remove drop listener - const prev = this._bound.get("drop-target"); - if (prev && prev.el) { - prev.el.removeEventListener("dragover", prev.onDragOver); - prev.el.removeEventListener("drop", prev.onDrop); - } - - // clear DOM - if (this.rootEl && this.rootEl.parentElement) { - this.rootEl.parentElement.removeChild(this.rootEl); - } - - // do not destroy viewport here (consumer may still use it) - this._bound.clear(); - } -} diff --git a/packages/frontend/assets/style/entry.css b/packages/frontend/assets/style/entry.css index 5c3c5a3..b3371bb 100644 --- a/packages/frontend/assets/style/entry.css +++ b/packages/frontend/assets/style/entry.css @@ -201,10 +201,26 @@ input[type="range"]::-webkit-slider-thumb:focus { } .FormifyForm { + flex-grow: 1; +} + +.FormifyInputs { + width: 80%; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.FormifyButton { + margin: auto; + height: 8rem; + width: 8rem; + border-radius: 5rem; } .FormifyBox { display: flex; + box-sizing: border-box; align-items: center; gap: 1rem; padding: 0.5rem; diff --git a/packages/frontend/assets/style/items/search.css b/packages/frontend/assets/style/items/search.css index 508df25..f3d9ff7 100644 --- a/packages/frontend/assets/style/items/search.css +++ b/packages/frontend/assets/style/items/search.css @@ -1,26 +1,20 @@ .Search { + height: 100%; display: flex; - /* overflow: hidden; */ flex-direction: column; - justify-content: stretch; - align-items: center; -} - -.Search main { - display: flex; - flex-direction: column; - justify-content: stretch; - align-items: center; overflow-y: scroll; - padding: 1rem 0; + align-items: center; + padding: 1rem; } -.Search header { +.Search > header { position: sticky; background-color: rgba(var(--background-highlight-color), 0.5); } -.Search .results { +.Search > main { + padding: 1rem 0; + flex-grow: 1; display: flex; flex-direction: column; gap: 1rem; diff --git a/packages/frontend/assets/style/items/search_result.css b/packages/frontend/assets/style/items/search_result.css index 0c0c159..6eb6e67 100644 --- a/packages/frontend/assets/style/items/search_result.css +++ b/packages/frontend/assets/style/items/search_result.css @@ -1,6 +1,6 @@ .SearchResult { background-color: hsl(var(--color-box-bg)); - flex-grow: 1; + flex-grow: 0; padding: 1rem; border-radius: 1rem; } diff --git a/packages/frontend/assets/style/pages/editor.css b/packages/frontend/assets/style/pages/editor.css index e923ef2..c5e8546 100644 --- a/packages/frontend/assets/style/pages/editor.css +++ b/packages/frontend/assets/style/pages/editor.css @@ -16,6 +16,4 @@ aside { background-color: hsl(var(--color-1), 10%); - backdrop-filter: blur(1rem); - padding: 1rem; } diff --git a/packages/frontend/assets/style/pages/new.css b/packages/frontend/assets/style/pages/new.css index fa34246..06a69a0 100644 --- a/packages/frontend/assets/style/pages/new.css +++ b/packages/frontend/assets/style/pages/new.css @@ -1,27 +1,24 @@ .New { display: flex; } - -.New .LabeledBox { - width: 100%; -} +/**/ +/* .New .LabeledBox { */ +/* width: 100%; */ +/* } */ .New button { height: 8rem; width: 8rem; border-radius: 5rem; - background-color: rgb(var(--background-highlight-color)); - border-style: none; - color: rgb(var(--text-color)); } -.New button .Icon { - scale: 1.2; - color: rgb(var(--text-color)); -} +/* .New button .Icon { */ +/* scale: 1.2; */ +/* color: rgb(var(--text-color)); */ +/* } */ -.New .button-wrapper { - display: flex; - justify-content: center; - align-items: center; -} +/* .New .button-wrapper { */ +/* display: flex; */ +/* justify-content: center; */ +/* align-items: center; */ +/* } */ diff --git a/packages/frontend/assets/style/pages/projects.css b/packages/frontend/assets/style/pages/projects.css index 1a238a6..73e4115 100644 --- a/packages/frontend/assets/style/pages/projects.css +++ b/packages/frontend/assets/style/pages/projects.css @@ -1,4 +1,6 @@ .Projects { + display: flex; + gap: 1rem; } .Projects .projects-wrapper { diff --git a/packages/frontend/src/modules/components/search.rs b/packages/frontend/src/modules/components/search.rs index 064d440..1098c29 100644 --- a/packages/frontend/src/modules/components/search.rs +++ b/packages/frontend/src/modules/components/search.rs @@ -5,6 +5,7 @@ use super::search_result::{InternSearchResult, SearchResult}; use super::utils::*; use chrono::Utc; use simple_ai_backend::modules::utils::node::NodeBuilder; +use tokio::time::*; // %% main %% #[item] @@ -47,29 +48,49 @@ pub fn Search(#[props(extends = GlobalAttributes)] attributes: Vec) - .unwrap(), ); + let future = use_resource(move || async move { + // You can create as many eval instances as you want + let mut eval = document::eval(r#"console.log("HELLOO")"#); + // let mut eval = document::eval( + // r#"console.log("helloworld"); const di = new window.OnnxDragIn(document.getElementById("onnx-drag-in"), document.getElementById("search"), document.getElementById("nodeContainer")); console.log(di);"#, + // ); + }); + rsx! { - article { class: "Search", ..attributes, + article { + class: "Search", + id: "onnx-drag-in", + onmounted: move |_| async move { + sleep(Duration::from_millis(100)).await; + _ = document::eval( + r#"const di = new window.OnnxDragIn(document.getElementById("onnx-drag-in"), document.getElementById("search"), document.getElementById("nodeContainer"));"#, + ); + }, + ..attributes, + document::Script { src: asset!("/assets/scripts/onnx-drag-in.js"), defer: true } header { - input { oninput: input, r#type: "search", placeholder: "search" } + input { + oninput: input, + r#type: "search", + placeholder: "search", + id: "search", + } // nav { // button { "your nodes" } // button { "installed nodes" } // button { "profiles" } // } } - main { - div { class: "spacer" } - section { class: "results", - for intern in intern_search_results() { - SearchResult { intern } - } - SearchResult { intern } - SearchResult { intern } - SearchResult { intern } - SearchResult { intern } - SearchResult { intern } + main { id: "nodeContainer", + for intern in intern_search_results() { SearchResult { intern } } + SearchResult { intern } + SearchResult { intern } + SearchResult { intern } + SearchResult { intern } + SearchResult { intern } + SearchResult { intern } } } } diff --git a/packages/frontend/src/modules/pages/editor.rs b/packages/frontend/src/modules/pages/editor.rs index 8da1283..df89d9e 100644 --- a/packages/frontend/src/modules/pages/editor.rs +++ b/packages/frontend/src/modules/pages/editor.rs @@ -1,26 +1,21 @@ -// %%% pages / editor.rs %%% - -// %% includes %% use super::utils::*; -use crate::modules::components::search::*; -use std::collections::HashMap; +use tokio::time::*; -// %% main %% #[page] pub fn Editor() -> Element { - let future = use_resource(move || async move { - // You can create as many eval instances as you want - let mut eval = document::eval( - r#"const vp = new window.Viewport(document.getElementById("viewport")); -vp.addNode(new window.VNode(100, 100, "A", [new window.Parameter("output", "outA")]));"#, - ); - }); rsx! { main { - section { id: "viewport", - document::Script { src: asset!("/assets/scripts/viewport.js") } - } + onmounted: move |e| async move { + sleep(Duration::from_millis(100)).await; + dioxus::document::eval( + r#" + window.activeOnnxViewport = new window.Viewport(document.getElementById("viewport")); + "#, + ); + }, + section { id: "viewport" } aside { Search {} } + document::Script { src: asset!("/assets/scripts/onnx-viewport.js") } } } } diff --git a/packages/frontend/src/modules/pages/projects.rs b/packages/frontend/src/modules/pages/projects.rs index c082859..7790933 100644 --- a/packages/frontend/src/modules/pages/projects.rs +++ b/packages/frontend/src/modules/pages/projects.rs @@ -7,6 +7,7 @@ use dioxus::router::NavigationTarget; pub fn Projects() -> Element { rsx! { main { + input { r#type: "search" } article { class: "projects-wrapper", div { class: "projects-view", Project { diff --git a/packages/macros/src/formifiable.rs b/packages/macros/src/formifiable.rs index 213f8dd..359157d 100644 --- a/packages/macros/src/formifiable.rs +++ b/packages/macros/src/formifiable.rs @@ -27,16 +27,24 @@ pub fn macro_impl(item: TokenStream) -> TokenStream { .to_tokens(&mut inputs); } quote! { - use dioxus::prelude::*; - impl #struct_ident { - pub fn rsx_form(&mut self) -> Element { - rsx! { - form { - class: "FormifyForm", - #inputs + use dioxus::prelude::*; + impl #struct_ident { + pub fn rsx_form(&mut self) -> Element { + rsx! { + form { + class: "FormifyForm", + + div { + class: "FormifyInputs", + #inputs + } + button { + class: "FormifyButton", + "confirm" + } + } } } } } - } } From 1b53ef411f32cf27da515835ad0e2c62a1cbf7b7 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 2 Dec 2025 18:13:58 +0100 Subject: [PATCH 14/17] added some animations --- packages/frontend/assets/style/entry.css | 5 ++++ .../assets/style/items/breadcrumbs.css | 23 ++++++++++++++++++- .../frontend/assets/style/items/project.css | 7 +++++- .../assets/style/items/window_decorations.css | 6 +++-- packages/frontend/assets/style/pages/new.css | 6 ----- .../src/modules/components/breadcrumbs.rs | 2 +- 6 files changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/frontend/assets/style/entry.css b/packages/frontend/assets/style/entry.css index b3371bb..66be090 100644 --- a/packages/frontend/assets/style/entry.css +++ b/packages/frontend/assets/style/entry.css @@ -92,6 +92,11 @@ button { border-radius: 3rem; border: none; cursor: pointer; + transition: padding 0.2s; +} + +button:hover { + padding: 1rem; } /* .NavbarButton { */ diff --git a/packages/frontend/assets/style/items/breadcrumbs.css b/packages/frontend/assets/style/items/breadcrumbs.css index 3098957..fc5886b 100644 --- a/packages/frontend/assets/style/items/breadcrumbs.css +++ b/packages/frontend/assets/style/items/breadcrumbs.css @@ -3,7 +3,7 @@ flex-direction: row; justify-content: left; align-items: center; - padding: 1rem; + padding: 0.5rem; box-sizing: content-box; position: sticky; } @@ -16,3 +16,24 @@ padding: 0; margin: 0; } + +/* .Breadcrumbs .Icon, */ +/* .Breadcrumbs p { */ +/* color: hsl(var(--color-1), 60%); */ +/* transition: color 0.5s; */ +/* } */ +/**/ +/* .Breadcrumbs .Icon:hover, */ +/* .Breadcrumbs p:hover { */ +/* color: hsl(var(--color-1), 90%); */ +/* } */ + +.Breadcrumbs a { + padding: 0.5rem; + border-radius: 1rem; + transition: 400ms background-color; +} + +.Breadcrumbs a:hover { + background-color: hsl(var(--color-1), 30%); +} diff --git a/packages/frontend/assets/style/items/project.css b/packages/frontend/assets/style/items/project.css index 98ec3aa..a1ea0b6 100644 --- a/packages/frontend/assets/style/items/project.css +++ b/packages/frontend/assets/style/items/project.css @@ -9,9 +9,14 @@ display: flex; flex-direction: row; align-items: center; - background-color: hsl(var(--color-box-bg)); + background-color: hsl(var(--color-box-bg), 70%); border-radius: var(--project-border-radius); gap: 1rem; + transition: background-color 1s; +} + +.Project:hover { + background-color: hsl(var(--color-box-bg), 100%); } .Project .divider { diff --git a/packages/frontend/assets/style/items/window_decorations.css b/packages/frontend/assets/style/items/window_decorations.css index 8c051d1..74b8d7f 100644 --- a/packages/frontend/assets/style/items/window_decorations.css +++ b/packages/frontend/assets/style/items/window_decorations.css @@ -6,6 +6,8 @@ .WindowDecorations .Icon { margin: 0; padding: 0.2rem; + border-radius: 5rem; + transition: background-color 200ms; } .WindowDecorations section { flex-grow: 1; @@ -15,7 +17,7 @@ align-items: center; cursor: pointer; } + .WindowDecorations .Icon:hover { - border-radius: 5rem; - background-color: rgba(var(--background-highlight-color), 0.3); + background-color: hsl(var(--color-1), 30%); } diff --git a/packages/frontend/assets/style/pages/new.css b/packages/frontend/assets/style/pages/new.css index 06a69a0..363a200 100644 --- a/packages/frontend/assets/style/pages/new.css +++ b/packages/frontend/assets/style/pages/new.css @@ -6,12 +6,6 @@ /* width: 100%; */ /* } */ -.New button { - height: 8rem; - width: 8rem; - border-radius: 5rem; -} - /* .New button .Icon { */ /* scale: 1.2; */ /* color: rgb(var(--text-color)); */ diff --git a/packages/frontend/src/modules/components/breadcrumbs.rs b/packages/frontend/src/modules/components/breadcrumbs.rs index 5055f8a..81b40f1 100644 --- a/packages/frontend/src/modules/components/breadcrumbs.rs +++ b/packages/frontend/src/modules/components/breadcrumbs.rs @@ -17,7 +17,7 @@ pub fn Breadcrumbs() -> Element { assembled.push_str(crumb); assembled.parse::>().unwrap() }, - {crumb.to_string()} + p { {crumb.to_string()} } } } } From f52b455c0eb0f3a1d901c77b7dd39b4762a7023f Mon Sep 17 00:00:00 2001 From: = Date: Wed, 3 Dec 2025 18:19:55 +0100 Subject: [PATCH 15/17] working on divider --- packages/frontend/assets/scripts/divider.js | 125 ++++++++++++++++++ .../frontend/assets/style/items/divider.css | 55 +++++--- .../src/modules/components/divider.rs | 43 +++--- packages/frontend/src/modules/pages/editor.rs | 5 +- 4 files changed, 187 insertions(+), 41 deletions(-) create mode 100644 packages/frontend/assets/scripts/divider.js diff --git a/packages/frontend/assets/scripts/divider.js b/packages/frontend/assets/scripts/divider.js new file mode 100644 index 0000000..ad85be9 --- /dev/null +++ b/packages/frontend/assets/scripts/divider.js @@ -0,0 +1,125 @@ +class Divider { + constructor(divider, orientation) { + if (!(divider instanceof HTMLElement)) { + throw new Error("Divider constructor requires a DOM container element"); + } + this.divider = divider; + this.orientation = orientation; + this.firstPane = this.divider.previousSibling; + this.secondPane = this.divider.nextSibling; + + console.log(this.firstPane, this.secondPane); + + this._startFirstPane = null; + this._startCursor = null; + + this._onMouseDown = this._onMouseDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + this._onMouseMove = this._onMouseUp.bind(this); + + switch (orientation) { + case "h": + this._getStartCursor = this._getHorizontalStartCursor; + this._getStartFirstPane = this._getHorizontalStartFirstPane; + this._getCursorStyle = this._getHorizontalCursorStyle; + this.divider.classList.add("horizontal"); + this._setSize = this._setHorizontalSize; + break; + + case "v": + this._getStartCursor = this._getVerticalStartCursor; + this._getStartFirstPane = this._getVerticalStartFirstPane; + this._getCursorStyle = this._getVerticalCursorStyle; + this.divider.classList.add("vertical"); + this._setSize = this._setVerticalSize; + break; + + default: + throw new Error( + "Please choose either vertical orientation -> 'v' or horizontal -> 'h'.", + ); + } + + this.divider.addEventListener("mousedown", this._onMouseDown); + window.addEventListener("mouseup", this._onMouseUp); + document.addEventListener("mousemove", this._onMouseMove); + + console.log(this); + } + + _getHorizontalStartCursor(e) { + return e.x; + } + + _getVerticalStartCursor(e) { + return e.y; + } + + _getHorizontalStartFirstPane() { + return this.firstPane.offsetHeight; + } + + _getVerticalStartFirstPane() { + return this.firstPane.offsetWidth; + } + + _getHorizontalCursorStyle() { + return "row-resize"; + } + + _getVerticalCursorStyle() { + return "col-resize"; + } + + _setVerticalSize(first, second) { + this.firstPane.style.width = first + "px"; + this.secondPane.style.width = second + "px"; + } + _setHorizontalSize(first, second) { + this.firstPane.style.height = first + "px"; + this.secondPane.style.height = second + "px"; + } + + _onMouseDown(e) { + this.isDragging = true; + this._startCursor = this._getStartCursor(e); + this._startFirstPane = this._getStartFirstPane(); + this.divider.classList.add("active"); + document.body.style.cursor = this._getCursorStyle(); + } + + _onMouseMove(e) { + console.log("movement"); + if (!this.isDragging) return; + const movement = e.clientY - this._startCursor; + + console.log(movement); + let newFirstPaneSize = this._startFirstPane + movement; + const containerRect = this.divider.parent.getBoundingClientRect(); + const minPaneSize = 30; + + // Clamp values + newFirstPaneSize = Math.max(newFirstPaneSize, minPaneSize); + newFirstPaneSize = Math.min( + newFirstPaneSize, + containerRect.height - minPaneSize - this.divider.offsetHeight, + ); + + this.firstPane.style.flex = "none"; + this.secondPane.style.flex = "none"; + this._setSize( + newTopHeight, + containerRect.height - newFirstPaneSize - this.divider.offsetHeight, + ); + } + + _onMouseUp(e) { + if (this.isDragging) { + this.isDragging = false; + this.divider.classList.remove("active"); + document.body.style.cursor = ""; + } + } +} + +window.Divider = Divider; diff --git a/packages/frontend/assets/style/items/divider.css b/packages/frontend/assets/style/items/divider.css index ecd8b6f..cfefdd1 100644 --- a/packages/frontend/assets/style/items/divider.css +++ b/packages/frontend/assets/style/items/divider.css @@ -1,25 +1,48 @@ .Divider { - display: flex; - align-items: center; - justify-content: stretch; + --divider-size: 0.5rem; + user-select: none; + transition: background 0.2s; } -.Divider > .wrapper { - height: 100%; - width: 1rem; - margin: 0 calc(-0.5rem + var(--border-width) / 2); - display: flex; - align-items: center; - justify-content: center; +.Divider.horizontal { + width: 100%; + height: var(--divider-size); + cursor: row-resize; } -.Divider > .wrapper:hover { +.Divider.vertical { + height: 100%; + width: var(--divider-size); cursor: col-resize; } -.Divider > .wrapper > .inner { - background-color: rgba(var(--background-highlight-color), 0.5); - height: 100%; - width: var(--border-width); - z-index: 100; +.Divider:hover, +.Divider.active { + background: hsl(var(--color-box-bg)); } + +/* .Divider { */ +/* display: flex; */ +/* align-items: center; */ +/* justify-content: stretch; */ +/* } */ +/**/ +/* .Divider > .wrapper { */ +/* height: 100%; */ +/* width: 1rem; */ +/* margin: 0 calc(-0.5rem + var(--border-width) / 2); */ +/* display: flex; */ +/* align-items: center; */ +/* justify-content: center; */ +/* } */ +/**/ +/* .Divider > .wrapper:hover { */ +/* cursor: col-resize; */ +/* } */ +/**/ +/* .Divider > .wrapper > .inner { */ +/* background-color: rgba(var(--background-highlight-color), 0.5); */ +/* height: 100%; */ +/* width: var(--border-width); */ +/* z-index: 100; */ +/* } */ diff --git a/packages/frontend/src/modules/components/divider.rs b/packages/frontend/src/modules/components/divider.rs index 8d3b943..be3f891 100644 --- a/packages/frontend/src/modules/components/divider.rs +++ b/packages/frontend/src/modules/components/divider.rs @@ -1,28 +1,25 @@ -// %%% components / divider.rs %%% - -// %% includes %% use super::utils::*; +use tokio::time::*; -// %% main %% -#[component] -pub fn Divider(children: Element) -> Element { - let script = r#" - let l = document.currentScript.parentElement; - let middleIndex = Math.floor(l.children.length / 2); - let wrapper = document.createElement("div"); - wrapper.className = "wrapper"; - let div = document.createElement("div"); - div.className = "inner"; - wrapper.appendChild(div); - l.insertBefore(wrapper, l.children[middleIndex + 1]); - "#; +#[item] +pub fn Divider(id: String, orientation: char, children: Element) -> Element { + let script: String = format!( + r#"console.log("before divider"); window.divider = new window.Divider(document.getElementById("{}"), "{}");"#, + id.clone(), + orientation, + ); rsx! { - article { - class: "Divider", - script { - { script } - } - { children } - } + div { + class: "Divider", + id, + onmounted: move |e| { + let script = script.clone(); + async move { + sleep(Duration::from_millis(100)).await; + dioxus::document::eval(&script); + } + }, + document::Script { src: asset!("/assets/scripts/divider.js") } + } } } diff --git a/packages/frontend/src/modules/pages/editor.rs b/packages/frontend/src/modules/pages/editor.rs index df89d9e..5f03856 100644 --- a/packages/frontend/src/modules/pages/editor.rs +++ b/packages/frontend/src/modules/pages/editor.rs @@ -9,11 +9,12 @@ pub fn Editor() -> Element { sleep(Duration::from_millis(100)).await; dioxus::document::eval( r#" - window.activeOnnxViewport = new window.Viewport(document.getElementById("viewport")); - "#, + window.activeOnnxViewport = new window.Viewport(document.getElementById("viewport")); + "#, ); }, section { id: "viewport" } + Divider { id: "editor-aside-viewport", orientation: 'v' } aside { Search {} } document::Script { src: asset!("/assets/scripts/onnx-viewport.js") } } From dfb2e5d0a8599155e5b27cf54ecbe18eeba3822e Mon Sep 17 00:00:00 2001 From: = Date: Thu, 4 Dec 2025 16:13:48 +0100 Subject: [PATCH 16/17] divider now working --- packages/frontend/assets/scripts/divider.js | 60 +++++++++++++------ .../frontend/assets/scripts/onnx-viewport.js | 5 +- .../frontend/assets/style/pages/editor.css | 1 + 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/packages/frontend/assets/scripts/divider.js b/packages/frontend/assets/scripts/divider.js index ad85be9..0022f53 100644 --- a/packages/frontend/assets/scripts/divider.js +++ b/packages/frontend/assets/scripts/divider.js @@ -8,14 +8,12 @@ class Divider { this.firstPane = this.divider.previousSibling; this.secondPane = this.divider.nextSibling; - console.log(this.firstPane, this.secondPane); - this._startFirstPane = null; this._startCursor = null; this._onMouseDown = this._onMouseDown.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); this._onMouseMove = this._onMouseMove.bind(this); - this._onMouseMove = this._onMouseUp.bind(this); switch (orientation) { case "h": @@ -24,6 +22,8 @@ class Divider { this._getCursorStyle = this._getHorizontalCursorStyle; this.divider.classList.add("horizontal"); this._setSize = this._setHorizontalSize; + this._getContainerSize = this._getHorizontalContainerSize; + this._getDividerSize = this._getHorizontalDividerSize; break; case "v": @@ -32,6 +32,8 @@ class Divider { this._getCursorStyle = this._getVerticalCursorStyle; this.divider.classList.add("vertical"); this._setSize = this._setVerticalSize; + this._getContainerSize = this._getVerticalContainerSize; + this._getDividerSize = this._getVerticalDividerSize; break; default: @@ -40,19 +42,26 @@ class Divider { ); } - this.divider.addEventListener("mousedown", this._onMouseDown); - window.addEventListener("mouseup", this._onMouseUp); - document.addEventListener("mousemove", this._onMouseMove); + this._addListeners(); + } - console.log(this); + _addListeners() { + this.divider.addEventListener("mousedown", (e) => { + this._onMouseDown(e); + window.addEventListener("mousemove", this._onMouseMove); + }); + window.addEventListener("mouseup", (e) => { + this._onMouseUp(e); + window.removeEventListener("mousemove", this._onMouseMove); + }); } _getHorizontalStartCursor(e) { - return e.x; + return e.y; } _getVerticalStartCursor(e) { - return e.y; + return e.x; } _getHorizontalStartFirstPane() { @@ -88,28 +97,41 @@ class Divider { document.body.style.cursor = this._getCursorStyle(); } + _getHorizontalContainerSize() { + return this.divider.parentElement.getBoundingClientRect().heigth; + } + + _getVerticalContainerSize() { + return this.divider.parentElement.getBoundingClientRect().width; + } + + _getHorizontalDividerSize() { + return this.divider.offsetHeight; + } + + _getVerticalDividerSize() { + return this.divider.offsetWidth; + } + _onMouseMove(e) { - console.log("movement"); if (!this.isDragging) return; - const movement = e.clientY - this._startCursor; + const movement = this._getStartCursor(e) - this._startCursor; - console.log(movement); let newFirstPaneSize = this._startFirstPane + movement; - const containerRect = this.divider.parent.getBoundingClientRect(); - const minPaneSize = 30; + const minPaneSize = 300; // Clamp values newFirstPaneSize = Math.max(newFirstPaneSize, minPaneSize); newFirstPaneSize = Math.min( newFirstPaneSize, - containerRect.height - minPaneSize - this.divider.offsetHeight, + this._getContainerSize() - minPaneSize - this._getDividerSize(), ); - this.firstPane.style.flex = "none"; - this.secondPane.style.flex = "none"; + console.log(newFirstPaneSize); + this._setSize( - newTopHeight, - containerRect.height - newFirstPaneSize - this.divider.offsetHeight, + newFirstPaneSize, + this._getContainerSize() - newFirstPaneSize - this._getDividerSize(), ); } diff --git a/packages/frontend/assets/scripts/onnx-viewport.js b/packages/frontend/assets/scripts/onnx-viewport.js index 1fcea00..47ae978 100644 --- a/packages/frontend/assets/scripts/onnx-viewport.js +++ b/packages/frontend/assets/scripts/onnx-viewport.js @@ -280,7 +280,8 @@ class Viewport { this._onKeyDown = this._onKeyDown.bind(this); // event listeners - window.addEventListener("resize", this._onResize); + this._containerResizeObserver = new ResizeObserver(this._onResize); + this._containerResizeObserver.observe(this.container); this.canvas.addEventListener("mousedown", this._onMouseDown); this.canvas.addEventListener("mousemove", this._onMouseMove); this.canvas.addEventListener("mouseup", this._onMouseUp); @@ -589,7 +590,7 @@ class Viewport { } destroy() { - window.removeEventListener("resize", this._onResize); + // window.removeEventListener("resize", this._onResize); this.canvas.removeEventListener("mousedown", this._onMouseDown); this.canvas.removeEventListener("mousemove", this._onMouseMove); this.canvas.removeEventListener("mouseup", this._onMouseUp); diff --git a/packages/frontend/assets/style/pages/editor.css b/packages/frontend/assets/style/pages/editor.css index c5e8546..8337703 100644 --- a/packages/frontend/assets/style/pages/editor.css +++ b/packages/frontend/assets/style/pages/editor.css @@ -15,5 +15,6 @@ } aside { + min-width: 300px; background-color: hsl(var(--color-1), 10%); } From 2013d0ceb4c3e7693d717ba3a11097ddf7a7d73d Mon Sep 17 00:00:00 2001 From: = Date: Thu, 4 Dec 2025 16:53:32 +0100 Subject: [PATCH 17/17] added todos --- packages/frontend/src/modules/pages/editor.rs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/modules/pages/editor.rs b/packages/frontend/src/modules/pages/editor.rs index 5f03856..d9ca1ee 100644 --- a/packages/frontend/src/modules/pages/editor.rs +++ b/packages/frontend/src/modules/pages/editor.rs @@ -1,17 +1,32 @@ use super::utils::*; use tokio::time::*; +const CREATE_VIEWPORT: &str = r#" + window.activeOnnxViewport = new window.Viewport(document.getElementById("viewport")); + // window.activeOnnxViewport.listener(); +"#; + +// TODO: +// - add a train button +// -> add something like simple_ai_backend :: onnx :: Project . train() +// - BUT FIRST: add a save button and +// -> js getNodesFunction +// -> convert to Node of backend +// -> add something like simple_ai_backend :: onnx :: Project . save_nodes(Vec) #[page] pub fn Editor() -> Element { rsx! { main { onmounted: move |e| async move { sleep(Duration::from_millis(100)).await; - dioxus::document::eval( - r#" - window.activeOnnxViewport = new window.Viewport(document.getElementById("viewport")); - "#, - ); + let mut viewport_listener = document::eval(CREATE_VIEWPORT); + // loop { + // let id: i32 = viewport_listener.recv().await.unwrap(); + // debug!("id {id}"); + // TODO: simple_ai_backend :: onnx :: fetch_node_from_id(id) -> Node; + // - Then make a function that converts the Node to the js one; + // - Lastly document::eval(format!(r#"window.activeOnnxViewport.addNode({})"#, node)); + // } }, section { id: "viewport" } Divider { id: "editor-aside-viewport", orientation: 'v' }