diff --git a/Cargo.toml b/Cargo.toml index d363a87..df6cd3c 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.1" } [profile] @@ -30,4 +30,5 @@ inherits = "dev" [profile.android-dev] inherits = "dev" - +[profile.dev] +overflow-checks = false 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 7e5cf3a..f06db88 100644 --- a/packages/frontend/Cargo.toml +++ b/packages/frontend/Cargo.toml @@ -7,14 +7,17 @@ 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" } +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" } +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/scripts/divider.js b/packages/frontend/assets/scripts/divider.js new file mode 100644 index 0000000..0022f53 --- /dev/null +++ b/packages/frontend/assets/scripts/divider.js @@ -0,0 +1,147 @@ +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; + + this._startFirstPane = null; + this._startCursor = null; + + this._onMouseDown = this._onMouseDown.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this._onMouseMove = this._onMouseMove.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; + this._getContainerSize = this._getHorizontalContainerSize; + this._getDividerSize = this._getHorizontalDividerSize; + break; + + case "v": + this._getStartCursor = this._getVerticalStartCursor; + this._getStartFirstPane = this._getVerticalStartFirstPane; + this._getCursorStyle = this._getVerticalCursorStyle; + this.divider.classList.add("vertical"); + this._setSize = this._setVerticalSize; + this._getContainerSize = this._getVerticalContainerSize; + this._getDividerSize = this._getVerticalDividerSize; + break; + + default: + throw new Error( + "Please choose either vertical orientation -> 'v' or horizontal -> 'h'.", + ); + } + + this._addListeners(); + } + + _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.y; + } + + _getVerticalStartCursor(e) { + return e.x; + } + + _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(); + } + + _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) { + if (!this.isDragging) return; + const movement = this._getStartCursor(e) - this._startCursor; + + let newFirstPaneSize = this._startFirstPane + movement; + const minPaneSize = 300; + + // Clamp values + newFirstPaneSize = Math.max(newFirstPaneSize, minPaneSize); + newFirstPaneSize = Math.min( + newFirstPaneSize, + this._getContainerSize() - minPaneSize - this._getDividerSize(), + ); + + console.log(newFirstPaneSize); + + this._setSize( + newFirstPaneSize, + this._getContainerSize() - newFirstPaneSize - this._getDividerSize(), + ); + } + + _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/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/onnx-viewport.js b/packages/frontend/assets/scripts/onnx-viewport.js new file mode 100644 index 0000000..47ae978 --- /dev/null +++ b/packages/frontend/assets/scripts/onnx-viewport.js @@ -0,0 +1,607 @@ +/** + * 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 = 10; + 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 = 0, y = 0, label = "Node", params = []) { + this.label = label; + // 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 = 50; + this.width = 160; + this.height = Math.max( + 48, + 32 + + Math.max(this.inputs.length, this.outputs.length) * this.paramSpacing, + ); + 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 + this.heading_space + i * this.paramSpacing, + }; + } + // compute output socket coordinates + getOutputCoords(i) { + return { + x: this.x + this.width, + y: this.y + this.heading_space + 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; + } + + // 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; + // 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; + 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; + } + + 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); + 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(); + } +} + +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; + + // internal state + this.zoom = options.zoom || 1; + this.offsetX = options.offsetX || 0; + this.offsetY = options.offsetY || 0; + + this.nodes = []; + this.connections = []; + + 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"); + + 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 + 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); + this.canvas.addEventListener("wheel", this._onWheel, { passive: false }); + document.addEventListener("keydown", this._onKeyDown); + + // initial sizing and draw + this._onResize(); + } + + // convenience factory + createNode(x, y, label, params = []) { + const node = new VNode(x, y, label, params); + this.addNode(node); + return node; + } + + addNode(node) { + this.nodes.push(node); + 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(); + } + + 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); + + // 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(); + } + } + + // Live connect preview + if (this.connectingFrom) { + const from = this.connectingFrom.node.getOutputCoords( + this.connectingFrom.outIdx, + ); + ctx.strokeStyle = "#fb0"; + ctx.lineWidth = 2; + ctx.beginPath(); + 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(); + } + + // 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(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 + ctx.setTransform(1, 0, 0, 1, 0, 0); + } + + // -------- Input handlers ---------- + _onMouseDown(e) { + const p = this.toEditor(e.offsetX, e.offsetY); + this.mouse = p; + + // output hit first (for drag to input) + for (let node of this.nodes) { + let outIdx = node.outputHit(p.x, p.y); + if (outIdx !== null) { + this.connectingFrom = { node, outIdx }; + e.preventDefault(); + this.selectedNode = null; + this.selectedConnection = null; + this.draw(); + return; + } + } + + // 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; + } + } + + // 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; + this.selectedNode = null; + this.selectedConnection = null; + this.draw(); + } + + _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(); + } + } + + _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 !== this.connectingFrom.node) { + this.connections.push( + new Connection( + this.connectingFrom.node, + this.connectingFrom.outIdx, + node, + inIdx, + ), + ); + break; + } + } + this.connectingFrom = null; + this.draw(); + } else if (this.isPanning) { + this.isPanning = false; + this.draw(); + } + } + + _onWheel(e) { + let scale = 1 + (e.deltaY < 0 ? 0.1 : -0.1); + let mx = e.offsetX, + my = e.offsetY; + 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(); + } + + _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); + } + } +} + +window.Parameter = Parameter; +window.VNode = VNode; +window.Viewport = Viewport; diff --git a/packages/frontend/assets/style/components/labeled_box.css b/packages/frontend/assets/style/components/labeled_box.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/style/components/nav_button.css b/packages/frontend/assets/style/components/nav_button.css deleted file mode 100644 index e69de29..0000000 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..66be090 --- /dev/null +++ b/packages/frontend/assets/style/entry.css @@ -0,0 +1,238 @@ +:root { + --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: 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; + --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%; + --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%; + + --border-width: 0.15rem; + --border-color: var(--accent-color); + --border-radius: 1rem; +} + +html, +body, +#main, +svg, +section { + height: 100%; + width: 100%; +} + +main, +article, +aside, +section { + flex-grow: 1; +} + +#main, +main { + display: flex; + flex-direction: column; +} + +/* % no spacing % */ +html, +body, +main, +section, +article, +aside, +div, +form, +p { + margin: 0; + padding: 0; +} + +* { + user-select: none; +} + +html { + background-color: hsl(var(--color-bg)); + color: hsl(var(--color-text-fg)); +} + +.Icon { + color: hsl(var(--color-icon-fg)); + background-color: hsl(var(--color-icon-bg)); + width: 2rem; + height: 2rem; + margin: 0.5rem; + padding: 0.5rem; +} + +button { + padding: 0.5rem; + display: flex; + background-color: hsl(var(--color-button-bg)); + color: hsl(var(--color-button-fg)); + justify-content: center; + align-items: center; + border-radius: 3rem; + border: none; + cursor: pointer; + transition: padding 0.2s; +} + +button:hover { + padding: 1rem; +} + +/* .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: hsl(var(--color-link-fg)); +} + +/* % h1 % */ +h1, +h2, +h3, +h4 { + padding: 0; + margin: 0; +} + +form { + align-items: center; + display: flex; + flex-direction: column; + justify-content: start; + gap: 2rem; + padding: 2rem; +} + +input { + 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: 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; +} + +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)); +} + +.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; +} + +.FormifyText { +} + +.FormifyInput { +} diff --git a/packages/frontend/assets/style/items/breadcrumbs.css b/packages/frontend/assets/style/items/breadcrumbs.css new file mode 100644 index 0000000..fc5886b --- /dev/null +++ b/packages/frontend/assets/style/items/breadcrumbs.css @@ -0,0 +1,39 @@ +.Breadcrumbs { + display: flex; + flex-direction: row; + justify-content: left; + align-items: center; + padding: 0.5rem; + box-sizing: content-box; + position: sticky; +} + +.Breadcrumbs .HomeIcon { + scale: 0.7; +} + +.Breadcrumbs .Icon { + 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/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..cfefdd1 --- /dev/null +++ b/packages/frontend/assets/style/items/divider.css @@ -0,0 +1,48 @@ +.Divider { + --divider-size: 0.5rem; + user-select: none; + transition: background 0.2s; +} + +.Divider.horizontal { + width: 100%; + height: var(--divider-size); + cursor: row-resize; +} + +.Divider.vertical { + height: 100%; + width: var(--divider-size); + cursor: col-resize; +} + +.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/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/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 new file mode 100644 index 0000000..9f12cea --- /dev/null +++ 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/nav_button.css b/packages/frontend/assets/style/items/nav_button.css new file mode 100644 index 0000000..e8e9876 --- /dev/null +++ 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/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/navigation_page.css b/packages/frontend/assets/style/items/navigation_page.css new file mode 100644 index 0000000..1dab3c3 --- /dev/null +++ b/packages/frontend/assets/style/items/navigation_page.css @@ -0,0 +1,25 @@ +.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; + 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/node.css b/packages/frontend/assets/style/items/node.css new file mode 100644 index 0000000..8d2cdbe --- /dev/null +++ b/packages/frontend/assets/style/items/node.css @@ -0,0 +1,4 @@ +.Node { + background-color: hsl(var(--color-box-bg)); + border-radius: 1rem; +} diff --git a/packages/frontend/assets/style/items/project.css b/packages/frontend/assets/style/items/project.css new file mode 100644 index 0000000..a1ea0b6 --- /dev/null +++ b/packages/frontend/assets/style/items/project.css @@ -0,0 +1,88 @@ +.Project { + --project-padding: 1rem; + --project-border-radius: 1rem; + --project-items-border-radius: calc( + var(--project-border-radius) - var(--project-padding) / 2 + ); + + padding: var(--project-padding); + display: flex; + flex-direction: row; + align-items: center; + 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 { + background-color: hsl(var(--color-text-fg)); + 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-evenly; + align-items: center; + gap: 1rem; +} +.Project .actions .Icon { + flex-grow: 1; + color: hsl(var(--color-text-fg)); + margin: 0; +} + +.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 .actions a { + opacity: 0.7; + scale: 1; + transition: opacity 0.3s ease-in-out; +} +.Project .actions a:hover { + opacity: 1; +} + +.Project .actions .open { + background-color: hsl(var(--color-ok-bg)); +} +.Project .actions .open:hover { + background-color: hsl(var(--color-ok-bg)); +} +.Project .actions .edit { + background-color: hsl(var(--color-edit-bg)); +} +.Project .actions .edit:hover { + background-color: hsl(var(--color-edit-bg)); +} +.Project .actions .delete { + background-color: hsl(var(--color-delete-bg)); +} +.Project .actions .delete:hover { + background-color: hsl(var(--color-delete-bg)); +} 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..f3d9ff7 --- /dev/null +++ b/packages/frontend/assets/style/items/search.css @@ -0,0 +1,21 @@ +.Search { + height: 100%; + display: flex; + flex-direction: column; + overflow-y: scroll; + align-items: center; + padding: 1rem; +} + +.Search > header { + position: sticky; + background-color: rgba(var(--background-highlight-color), 0.5); +} + +.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 new file mode 100644 index 0000000..6eb6e67 --- /dev/null +++ b/packages/frontend/assets/style/items/search_result.css @@ -0,0 +1,6 @@ +.SearchResult { + background-color: hsl(var(--color-box-bg)); + flex-grow: 0; + padding: 1rem; + border-radius: 1rem; +} 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/top_nav.css b/packages/frontend/assets/style/items/top_nav.css new file mode 100644 index 0000000..4d2357b --- /dev/null +++ b/packages/frontend/assets/style/items/top_nav.css @@ -0,0 +1,7 @@ +.TopNav { + width: 100%; + display: flex; + flex-direction: row; + background-color: hsl(var(--color-box-bg)); + 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/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/items/window_decorations.css b/packages/frontend/assets/style/items/window_decorations.css new file mode 100644 index 0000000..74b8d7f --- /dev/null +++ b/packages/frontend/assets/style/items/window_decorations.css @@ -0,0 +1,23 @@ +.WindowDecorations { + display: flex; + flex-direction: row; + align-items: center; +} +.WindowDecorations .Icon { + margin: 0; + padding: 0.2rem; + border-radius: 5rem; + transition: background-color 200ms; +} +.WindowDecorations section { + flex-grow: 1; + display: flex; + flex-direction: row; + margin: 0 0.5rem; + align-items: center; + cursor: pointer; +} + +.WindowDecorations .Icon:hover { + background-color: hsl(var(--color-1), 30%); +} diff --git a/packages/frontend/assets/style/main.css b/packages/frontend/assets/style/main.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/frontend/assets/style/pages/editor.css b/packages/frontend/assets/style/pages/editor.css index e69de29..8337703 100644 --- a/packages/frontend/assets/style/pages/editor.css +++ b/packages/frontend/assets/style/pages/editor.css @@ -0,0 +1,20 @@ +#viewport { + display: flex; + flex-grow: 1; + overflow: hidden; +} + +#paint { + flex-grow: 1; +} + +.Editor { + display: flex; + flex-direction: row; + overflow: hidden; +} + +aside { + min-width: 300px; + background-color: hsl(var(--color-1), 10%); +} diff --git a/packages/frontend/assets/style/pages/new.css b/packages/frontend/assets/style/pages/new.css index e69de29..363a200 100644 --- a/packages/frontend/assets/style/pages/new.css +++ b/packages/frontend/assets/style/pages/new.css @@ -0,0 +1,18 @@ +.New { + display: flex; +} +/**/ +/* .New .LabeledBox { */ +/* width: 100%; */ +/* } */ + +/* .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 new file mode 100644 index 0000000..73e4115 --- /dev/null +++ b/packages/frontend/assets/style/pages/projects.css @@ -0,0 +1,14 @@ +.Projects { + display: flex; + gap: 1rem; +} + +.Projects .projects-wrapper { + padding: 1rem 0; +} + +.Projects .projects-view { + display: flex; + flex-direction: column; + gap: 1rem; +} 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/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/lib.rs b/packages/frontend/src/lib.rs index cff0af7..a7271ae 100644 --- a/packages/frontend/src/lib.rs +++ b/packages/frontend/src/lib.rs @@ -1,11 +1,25 @@ +// %%% 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::*; + // % 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 7ae84f4..1dfcb42 100644 --- a/packages/frontend/src/modules.rs +++ b/packages/frontend/src/modules.rs @@ -1,4 +1,7 @@ +// %%% modules.rs %%% + +// %% exports %% pub mod components; -pub mod config; -pub mod global; +pub mod icons; pub mod pages; +pub mod router; diff --git a/packages/frontend/src/modules/components.rs b/packages/frontend/src/modules/components.rs index a671c6d..f8f493d 100644 --- a/packages/frontend/src/modules/components.rs +++ b/packages/frontend/src/modules/components.rs @@ -1,29 +1,59 @@ +// %%% components.rs %%% + +// %% exports %% +pub mod breadcrumbs; pub mod connection; pub mod divider; 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_link; +pub mod navigation_page; pub mod node; +pub mod project; pub mod runtime_param; 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 { + pub use super::breadcrumbs::*; 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::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; 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::*; pub use super::super::static_param::*; } } + +// %% utils %% +pub(crate) mod 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..81b40f1 --- /dev/null +++ b/packages/frontend/src/modules/components/breadcrumbs.rs @@ -0,0 +1,25 @@ +use super::utils::*; + +#[item] +pub fn Breadcrumbs() -> Element { + let route_str = router().full_route_string(); + let mut crumbs: Vec<&str> = route_str.split_terminator("/").collect(); + crumbs.remove(0); + let mut assembled = String::new(); + rsx! { + main { + Link { to: Route::Start {}, HomeIcon {} } + for crumb in crumbs.iter() { + RightBracketIcon {} + Link { + to: { + assembled.push('/'); + assembled.push_str(crumb); + assembled.parse::>().unwrap() + }, + p { {crumb.to_string()} } + } + } + } + } +} diff --git a/packages/frontend/src/modules/components/connection.rs b/packages/frontend/src/modules/components/connection.rs index 1aa1d71..43e91d8 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 { @@ -118,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 81a800c..be3f891 100644 --- a/packages/frontend/src/modules/components/divider.rs +++ b/packages/frontend/src/modules/components/divider.rs @@ -1,26 +1,25 @@ -use crate::prelude::*; +use super::utils::*; +use tokio::time::*; -#[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); -"#####; +#[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! { - div { - 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/components/drag_area.rs b/packages/frontend/src/modules/components/drag_area.rs index cc72f42..87ba18a 100644 --- a/packages/frontend/src/modules/components/drag_area.rs +++ b/packages/frontend/src/modules/components/drag_area.rs @@ -1,10 +1,15 @@ -use crate::prelude::*; +// %%% components / rag_area.rs %%% +// %% includes %% +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 { @@ -27,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()); } } @@ -39,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 995dd74..0a4d569 100644 --- a/packages/frontend/src/modules/components/draggable.rs +++ b/packages/frontend/src/modules/components/draggable.rs @@ -1,78 +1,125 @@ -use crate::prelude::{components::prelude::*, *}; +// %%% components / draggable.rs %%% +// %% includes %% +use super::drag_area::DragContext; +use super::utils::*; +use dioxus::html::geometry::euclid::default::SideOffsets2D; +use dioxus::html::geometry::euclid::Rect; + +// %% main %% #[component] 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 mut set_dims = move || { + height_handle.set(format!("{}px", dims_handle().y)); + width_handle.set(format!("{}px", dims_handle().x)); }; - let context: DragContext = use_context(); + 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}", @@ -81,8 +128,9 @@ pub fn Draggable( transform: "translate({position_handle.read().x}px, {position_handle.read().y}px)", onmousedown: mousedown, onmounted: mounted, - onmouseover: mouseover, - { children } + 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/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 4c9770f..95987dc 100644 --- a/packages/frontend/src/modules/components/labeled_box.rs +++ b/packages/frontend/src/modules/components/labeled_box.rs @@ -1,23 +1,16 @@ -use crate::prelude::*; +use super::utils::*; -#[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 } + h5 { {name.clone()} } + input { + name, + required, + placeholder, + r#type: kind, + } } } } diff --git a/packages/frontend/src/modules/components/nav_button.rs b/packages/frontend/src/modules/components/nav_button.rs index 9a714a6..a1e19da 100644 --- a/packages/frontend/src/modules/components/nav_button.rs +++ b/packages/frontend/src/modules/components/nav_button.rs @@ -1,20 +1,20 @@ -use crate::prelude::*; -use dioxus::router::prelude::*; +use super::utils::*; +use dioxus::{core::AttributeValue, router::NavigationTarget}; -#[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 {class_unw}", - to: to, - div { - {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/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/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/node.rs b/packages/frontend/src/modules/components/node.rs index f38c470..449cae3 100644 --- a/packages/frontend/src/modules/components/node.rs +++ b/packages/frontend/src/modules/components/node.rs @@ -1,6 +1,16 @@ -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 %% +// % transferrer % // +pub static NODE_TRANSFERER: GlobalSignal> = + GlobalSignal::new(|| None); + +// % Node % // #[derive(PartialEq, Props, Clone)] pub struct InternNode { pub node: StrongNode, @@ -72,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 { @@ -88,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/project.rs b/packages/frontend/src/modules/components/project.rs new file mode 100644 index 0000000..ac60af7 --- /dev/null +++ b/packages/frontend/src/modules/components/project.rs @@ -0,0 +1,23 @@ +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} } + } + div { class: "divider" } + 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/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..1098c29 100644 --- a/packages/frontend/src/modules/components/search.rs +++ b/packages/frontend/src/modules/components/search.rs @@ -1,54 +1,96 @@ -use crate::prelude::{ - components::prelude::{InternSearchResult, SearchResult}, - *, -}; -use simple_ai_backend::utils::prelude::*; +// %%% components / search.rs %%% -#[component] -pub fn Search() -> Element { +// %% includes %% +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] +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(), + ); + + 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! { - div { - overflow: "visible", + 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, - type: "search", - placeholder: "search" + r#type: "search", + placeholder: "search", + id: "search", } - // nav { - // button { "your nodes" } - // button { "installed nodes" } - // button { "profiles" } - // } + // nav { + // button { "your nodes" } + // button { "installed nodes" } + // button { "profiles" } + // } } - main { - overflow: "visible", - div { class: "spacer" } - section { - class: "results", - height: "100%", - for intern in intern_search_results() { - SearchResult { intern } - } + 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/components/search_result.rs b/packages/frontend/src/modules/components/search_result.rs index 8269ab0..62b05b5 100644 --- a/packages/frontend/src/modules/components/search_result.rs +++ b/packages/frontend/src/modules/components/search_result.rs @@ -1,6 +1,13 @@ -use crate::prelude::{components::prelude::Draggable, *}; -use simple_ai_backend::utils::prelude::*; +// %%% components / search_result.rs %%% +// %% 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, @@ -12,42 +19,20 @@ impl From for InternSearchResult { } } -#[component] +#[item] 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! { - 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/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 3f375d2..e508d85 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::*; -use simple_ai_backend::utils::prelude::*; +// %%% components / static_param.rs %%% +// %% includes %% +use super::utils::*; +use simple_ai_backend::prelude::*; + +// %% main %% #[derive(PartialEq, Props, Clone)] pub struct InternStaticParam { pub param: StrongParam, 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..3f6f3d4 --- /dev/null +++ b/packages/frontend/src/modules/components/top_nav.rs @@ -0,0 +1,35 @@ +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 { onmousedown: move |_| { window().drag() }, + Breadcrumbs {} + WindowDecorations {} + } + } +} + +#[cfg(target_family = "wasm")] +#[item] +pub fn TopNav() -> Element { + rsx! { + div { + } + } +} + +#[item] +pub fn TopNavLayout() -> Element { + rsx! { + div { + TopNav {} + article { Outlet:: {} } + } + } +} diff --git a/packages/frontend/src/modules/components/viewport.rs b/packages/frontend/src/modules/components/viewport.rs index 3106f46..99d9af8 100644 --- a/packages/frontend/src/modules/components/viewport.rs +++ b/packages/frontend/src/modules/components/viewport.rs @@ -1,6 +1,12 @@ -use crate::prelude::*; -use simple_ai_backend::utils::prelude::*; +// %%% components / viewport.rs %%% +use crate::modules::pages::utils::NODE_TRANSFERER; + +// %% includes %% +use super::utils::*; +use simple_ai_backend::prelude::*; + +// %% main %% #[derive(Clone)] pub struct ViewportNodeContainer { pub backend_node_container: NodeContainer, @@ -71,8 +77,8 @@ pub fn Viewport( } }; - use_resource(move || async move { - let mut ctx = DRAG_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()) @@ -130,9 +136,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()); @@ -166,7 +172,7 @@ pub fn Viewport( }; rsx! { - body { + article { class: "Viewport", cursor: "{cursor}", overflow: "hidden", 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..8763596 --- /dev/null +++ b/packages/frontend/src/modules/components/window_decorations.rs @@ -0,0 +1,29 @@ +use super::utils::*; + +#[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 { + } + } + } +} 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..7780211 --- /dev/null +++ b/packages/frontend/src/modules/icons.rs @@ -0,0 +1,15 @@ +use crate::utils::*; + +icon! { + settings: , + folder_open: , + trash: , + close: , + right_bracket: , + home: , + projects: , + search: , + new: , + editor: , + market: +} diff --git a/packages/frontend/src/modules/pages.rs b/packages/frontend/src/modules/pages.rs index 36808da..4f433c9 100644 --- a/packages/frontend/src/modules/pages.rs +++ b/packages/frontend/src/modules/pages.rs @@ -1,11 +1,25 @@ +// %%% pages.rs %%% + +// %% exports %% pub mod editor; pub mod new; +pub mod project_nav; +pub mod projects; pub mod search; pub mod start; +// %% prelude %% 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; } + +// %% 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..d9ca1ee 100644 --- a/packages/frontend/src/modules/pages/editor.rs +++ b/packages/frontend/src/modules/pages/editor.rs @@ -1,25 +1,37 @@ -use crate::prelude::{components::prelude::*, *}; +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! { - style { "html {{overflow: hidden;}} * {{ user_select: none }}" } main { - DragArea { - Divider - { - section { - Viewport {} - } - aside { - z_index: 2, - nav {} - section { - Search {} - } - } - } - } + onmounted: move |e| async move { + sleep(Duration::from_millis(100)).await; + 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' } + aside { Search {} } + document::Script { src: asset!("/assets/scripts/onnx-viewport.js") } } } } diff --git a/packages/frontend/src/modules/pages/new.rs b/packages/frontend/src/modules/pages/new.rs index cc72d57..fe54641 100644 --- a/packages/frontend/src/modules/pages/new.rs +++ b/packages/frontend/src/modules/pages/new.rs @@ -1,41 +1,37 @@ -use crate::prelude::{components::prelude::*, *}; +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 { - label { for: "name", "node name" } - input { id: "name", name: "name", type: "text", required: "true", placeholder: "SampleProject"} - } - LabeledBox { - input { id: "name", name: "name", type: "text", required: "true", placeholder: "SampleProject"} - label { for: "name", "project name" } - } - input { type: "text" } - LabeledBox { - label { for: "name", "project name" } - input { id: "name", name: "name", type: "text", required: "true", placeholder: "SampleProject"} - } - LabeledBox { - input { id: "name", name: "name", type: "text", required: "true", placeholder: "SampleProject"} - label { for: "name", "project name" } - } - input { type: "file" } - input { type: "range" } - - input { type: "list", list: "options" } - - datalist { - id: "options", - option { value: "1", "1" } - option { value: "2", "2" } - option { value: "3", "3" } - } - - button { type: "submit", "create" } - } + {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/frontend/src/modules/pages/project_nav.rs b/packages/frontend/src/modules/pages/project_nav.rs new file mode 100644 index 0000000..ab5d878 --- /dev/null +++ b/packages/frontend/src/modules/pages/project_nav.rs @@ -0,0 +1,12 @@ +use super::utils::*; + +#[page(no_css = true)] +pub fn ProjectNav() -> Element { + rsx! { + main { + NavigationPage { + NavigationLink { to: Route::Editor {}, label: "onnx editor", ProjectsIcon {} } + } + } + } +} diff --git a/packages/frontend/src/modules/pages/projects.rs b/packages/frontend/src/modules/pages/projects.rs new file mode 100644 index 0000000..7790933 --- /dev/null +++ b/packages/frontend/src/modules/pages/projects.rs @@ -0,0 +1,37 @@ +use super::super::components::project::Project; +use super::utils::*; +use chrono::{DateTime, Utc}; +use dioxus::router::NavigationTarget; + +#[page] +pub fn Projects() -> Element { + rsx! { + main { + input { r#type: "search" } + 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/search.rs b/packages/frontend/src/modules/pages/search.rs index e34bfc0..a60d5f5 100644 --- a/packages/frontend/src/modules/pages/search.rs +++ b/packages/frontend/src/modules/pages/search.rs @@ -1,10 +1,12 @@ -use crate::prelude::{components::prelude::*, *}; +// %%% pages / search.rs %%% +// %% includes %% +use super::utils::*; + +// %% main %% #[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 b16190e..99780f4 100644 --- a/packages/frontend/src/modules/pages/start.rs +++ b/packages/frontend/src/modules/pages/start.rs @@ -1,44 +1,16 @@ -pub use crate::prelude::{components::prelude::*, *}; -use dioxus::router::prelude::*; +// %%% pages / start.rs %%% +// %% includes %% +use super::utils::*; + +// %% 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 { - 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" } - } - - } } - } + NavigationPage { + NavigationLink { to: Route::Projects {}, label: "projects", ProjectsIcon {} } + NavigationLink { to: Route::New {}, label: "new project", NewIcon {} } } } } diff --git a/packages/frontend/src/modules/router.rs b/packages/frontend/src/modules/router.rs new file mode 100644 index 0000000..a3eb764 --- /dev/null +++ b/packages/frontend/src/modules/router.rs @@ -0,0 +1,33 @@ +pub mod core { + use super::super::{ + components::{heading_layout::HeadingLayout, top_nav::TopNavLayout}, + pages::prelude::*, + }; + use crate::utils::*; + + #[derive(Debug, Clone, Routable, PartialEq)] + #[rustfmt::skip] + pub enum Route { + #[layout(TopNavLayout)] + #[route("/")] + Start {}, + #[nest("/editor")] + #[route("/")] + Editor {}, + #[end_nest] + #[layout(HeadingLayout)] + #[nest("/projects")] + #[route("/")] + Projects {}, + #[route("/projects_nav")] + ProjectNav {}, + #[end_nest] + #[nest("/new")] + #[route("/")] + New {}, + } +} + +pub mod prelude { + pub use super::core::Route; +} diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml index 39bc7e7..49d3a16 100644 --- a/packages/macros/Cargo.toml +++ b/packages/macros/Cargo.toml @@ -1,13 +1,24 @@ [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" } +toml = { version = "0.9" } +serde = { version = "1.0" } +dioxus = { version = "0.7.1" } +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 e669d6f..de38590 100644 --- a/packages/macros/src/element.rs +++ b/packages/macros/src/element.rs @@ -1,52 +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 str_asset_path = std::path::PathBuf::from("/assets") - .join("style") - .join(str_type_name) - .join(str_lowercase_emt_name + ".css"); - - let asset_path = LitStr::new( - str_asset_path.to_str().unwrap_or_default(), - Span::call_site(), - ); - - for stmt in &mut out_items.block.stmts { - if let Stmt::Expr(syn::Expr::Macro(macro_expr), _) = stmt { - if macro_expr.mac.path.is_ident("rsx") { - let rsx_tokens = macro_expr.mac.tokens.clone(); - - macro_expr.mac.tokens = { - let mut rsx_block: Block = - parse2(rsx_tokens).expect("Failed to parse rsx! block"); - - let new_link: Stmt = parse_quote! { - document::Link { rel: "stylesheet", href: NAVBAR_CSS }, - }; - - rsx_block.stmts.insert(0, new_link); - - rsx_block.to_token_stream() - }; - } - } - } - - quote! { - const #css_emt_name: Asset = asset!(#asset_path); - #[dioxus::core_macro::component] - #out_items - } +// %%% 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..20ed005 --- /dev/null +++ b/packages/macros/src/element/function.rs @@ -0,0 +1,62 @@ +use crate::element::function; + +// %%% function.rs %%% +// %% includes %% +// % intern % +use super::rsx_ast::Element; +// % extern % +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{ + parse::{Parse, ParseStream}, + parse2, ItemFn, Macro, Stmt, StmtMacro, +}; +// % intern % +// %% main %% +// % Element Function % +#[derive(Clone)] +pub struct ElementFunction { + pub function: ItemFn, + pub name: String, + pub macro_ast: Element, +} + +impl ElementFunction { + 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 mut function: ItemFn = ItemFn::parse(input)?; + let name = function.sig.ident.to_string(); + + let macro_ast = { + let mac = Self::extract_macro_mut(&mut function)?; + parse2(mac.tokens.clone())? + }; + + Ok(Self { + function, + 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 new file mode 100644 index 0000000..5eb720b --- /dev/null +++ b/packages/macros/src/element/handler.rs @@ -0,0 +1,120 @@ +use proc_macro2::{Span, TokenStream}; +use quote::*; +use syn::{parse2, parse_quote, LitStr}; + +use super::{ + attrs::ElementAttrs, function::ElementFunction, kind::ElementKind, manifest::ElementConfig, +}; + +pub struct ElementHandler { + pub attrs: ElementAttrs, + pub function: ElementFunction, + pub config: ElementConfig, +} + +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"), + 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(); + } + } + + pub fn handle(&mut self) -> TokenStream { + self.handle_css(); + self.handle_as_entries(); + self.handle_class(); + + let func = &self.function; + + println!("{}", func.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()); + + 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) = self + .function + .macro_ast + .attrs + .iter_mut() + .find(|attr| attr.name == "class") + { + elm + } else { + 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() + } + }; + 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(), + )); + } +} 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..4e1675b --- /dev/null +++ b/packages/macros/src/element/rsx_ast.rs @@ -0,0 +1,120 @@ +// %%% rsx_body.rs %%% +// %% includes %% + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + braced, + parse::{Parse, ParseStream}, + parse2, + punctuated::Punctuated, + token::Brace, + Ident, LitStr, Macro, Path, Token, +}; + +use dioxus_rsx::CallBody; + +// %% main %% +// % Element % +#[derive(Clone)] +pub struct Element { + pub path: Path, + pub _brace: Brace, + pub attrs: Punctuated, + pub body: TokenStream, +} + +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; + 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, + _brace, + attrs, + body, + }) + } +} + +impl ToTokens for Element { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.path.to_tokens(tokens); + + let attrs = &self.attrs; + let body = &self.body; + let braced_group = quote! { + { + #attrs + #body + } + }; + tokens.extend(braced_group); + } +} + +#[derive(Clone)] +pub struct Attribute { + pub spread: Option, + pub name: Ident, + pub colon: Option, + pub value: Option, +} + +impl Parse for Attribute { + fn parse(input: ParseStream) -> syn::Result { + 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() { + input.parse().ok() + } 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/macros/src/entry.rs b/packages/macros/src/entry.rs new file mode 100644 index 0000000..b2c9b49 --- /dev/null +++ b/packages/macros/src/entry.rs @@ -0,0 +1,5 @@ +use crate::element::kind::*; +use proc_macro2::TokenStream; +pub fn macro_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + ElementKind::Entry {}.extra_macro_impl(attr, item) +} diff --git a/packages/macros/src/formifiable.rs b/packages/macros/src/formifiable.rs new file mode 100644 index 0000000..359157d --- /dev/null +++ b/packages/macros/src/formifiable.rs @@ -0,0 +1,50 @@ +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", + + div { + class: "FormifyInputs", + #inputs + } + button { + class: "FormifyButton", + "confirm" + } + } + } + } + } + } +} diff --git a/packages/macros/src/icon.rs b/packages/macros/src/icon.rs new file mode 100644 index 0000000..e5f4011 --- /dev/null +++ b/packages/macros/src/icon.rs @@ -0,0 +1,124 @@ +// %%% 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); + } + 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::{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()); + 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(); + println!("svg: {}", svg); + let classes = format!("Icon {}", function_name); + quote! { + #[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, + // ..attributes, + #svg + } + } + } + } + .to_tokens(&mut out); + } + println!( + "\n -------------------------------------------- ICON OUT:\n{}", + out + ); + out +} 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 239a657..0b1f1d3 100644 --- a/packages/macros/src/lib.rs +++ b/packages/macros/src/lib.rs @@ -1,17 +1,49 @@ +mod tests; + +mod icon; +#[proc_macro] +pub fn icon(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + icon::macro_impl(syn::parse_macro_input!(item)).into() +} + 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() - } - }; +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() } -create_attr_macro!(component); -create_attr_macro!(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() +} + +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() +} + +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() +} 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..1bff629 100644 --- a/packages/platforms/desktop/Cargo.toml +++ b/packages/platforms/desktop/Cargo.toml @@ -6,3 +6,7 @@ edition = "2021" [dependencies] 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 4e7a855..4d0903b 100644 --- a/packages/platforms/desktop/src/main.rs +++ b/packages/platforms/desktop/src/main.rs @@ -1,18 +1,21 @@ -mod router; +pub mod platform; pub mod prelude { + pub(crate) use super::App; pub(crate) use dioxus::prelude::*; pub(crate) use simple_ai_frontend::prelude::*; } use prelude::*; -use router::Route; fn main() { dioxus::logger::init(dioxus::logger::tracing::Level::DEBUG).expect("failed to init logger"); - dioxus::launch(App); + platform::launch(); } +#[component] fn App() -> Element { - rsx! { Router:: {} } + rsx! { + Router:: {} + } } 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()), + ); +} diff --git a/packages/platforms/desktop/src/router.rs b/packages/platforms/desktop/src/router.rs deleted file mode 100644 index b12d7f4..0000000 --- a/packages/platforms/desktop/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 {}, -} 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 {}, -}