feat: add validation to include_definition macro
This commit is contained in:
@@ -8,5 +8,7 @@ proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1.0", features = ["full"] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
|
||||
quote = "1.0"
|
||||
types = { version = "0.1.0", path = "../types" }
|
||||
|
@@ -1,18 +1,18 @@
|
||||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use serde_json::Value;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use syn::{parse_macro_input, LitStr};
|
||||
use types::NodeType;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn define_node(input: TokenStream) -> TokenStream {
|
||||
let input_string = parse_macro_input!(input as LitStr).value();
|
||||
|
||||
// Validate JSON format
|
||||
let json: Value = match serde_json::from_str(&input_string) {
|
||||
let json: NodeType = match serde_json::from_str(&input_string) {
|
||||
Ok(json) => json,
|
||||
Err(e) => panic!("Invalid JSON input: {}", e),
|
||||
};
|
||||
@@ -49,8 +49,8 @@ pub fn include_definition_file(input: TokenStream) -> TokenStream {
|
||||
});
|
||||
|
||||
// Optionally, validate that the content is valid JSON
|
||||
let _: Value = serde_json::from_str(&json_content)
|
||||
.unwrap_or_else(|_| panic!("JSON file contains invalid JSON"));
|
||||
let _: NodeType = serde_json::from_str(&json_content)
|
||||
.unwrap_or_else(|err| panic!("JSON file contains invalid JSON: {}", err));
|
||||
|
||||
// Generate the function that returns the JSON string
|
||||
let expanded = quote! {
|
||||
|
@@ -24,7 +24,7 @@
|
||||
"eslint-plugin-svelte": "^2.37.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-svelte": "^3.2.3",
|
||||
"svelte": "^4.2.14",
|
||||
"svelte": "^4.2.15",
|
||||
"svelte-check": "^3.6.9",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.4.5",
|
||||
|
8
packages/types/Cargo.toml
Normal file
8
packages/types/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "types"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
|
@@ -2,7 +2,7 @@
|
||||
"name": "@nodes/types",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.ts",
|
||||
"main": "src/index.ts",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
|
@@ -35,7 +35,7 @@ type DefaultOptions = {
|
||||
internal?: boolean;
|
||||
external?: boolean;
|
||||
setting?: string;
|
||||
label?: string;
|
||||
label?: string | false;
|
||||
}
|
||||
|
||||
type InputTypes = (NodeInputSeed | NodeInputBoolean | NodeInputFloat | NodeInputInteger | NodeInputSelect);
|
133
packages/types/src/lib.rs
Normal file
133
packages/types/src/lib.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum InputTypes {
|
||||
float(NodeInputFloat),
|
||||
integer(NodeInputInteger),
|
||||
boolean(NodeInputBoolean),
|
||||
select(NodeInputSelect),
|
||||
seed(NodeInputSeed),
|
||||
model(NodeInputModel),
|
||||
plant(NodeInputPlant),
|
||||
vec3(NodeInputVec3),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct NodeInputVec3 {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<Vec<f64>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct NodeInputFloat {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub min: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub step: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct NodeInputInteger {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub element: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub min: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct NodeInputBoolean {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct NodeInputSelect {
|
||||
pub labels: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct NodeInputSeed {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<usize>,
|
||||
}
|
||||
|
||||
// Assuming similar structure as other NodeInput types for Model and Plant
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct NodeInputModel {
|
||||
// Model-specific fields can be added here
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct NodeInputPlant {
|
||||
// Plant-specific fields can be added here
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct DefaultOptions {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub internal: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub external: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub setting: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub label: Option<serde_json::Value>, // To handle both String and false
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum NodeTypeOrArray {
|
||||
Single(InputTypes),
|
||||
Multiple(Vec<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NodeInput {
|
||||
pub types: Vec<String>,
|
||||
pub options: DefaultOptions,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NodeInput {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let raw_input: Value = Deserialize::deserialize(deserializer)?;
|
||||
let options: DefaultOptions =
|
||||
DefaultOptions::deserialize(&raw_input).map_err(serde::de::Error::custom)?; // Maps deserialization errors appropriately
|
||||
|
||||
let types: Vec<String> = match raw_input.get("type") {
|
||||
Some(Value::String(single_type)) => vec![single_type.clone()],
|
||||
Some(Value::Array(types)) => types
|
||||
.iter()
|
||||
.map(|t| t.as_str().unwrap_or("").to_owned())
|
||||
.collect(),
|
||||
_ => return Err(serde::de::Error::custom("Invalid or missing 'type' field")),
|
||||
};
|
||||
|
||||
Ok(NodeInput { types, options })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Serialize)]
|
||||
pub struct NodeType {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub inputs: Option<HashMap<String, NodeInput>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub outputs: Option<Vec<String>>,
|
||||
}
|
||||
|
@@ -16,7 +16,8 @@
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"svelte": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"./app.css": "./dist/app.css"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -37,7 +38,7 @@
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-plugin-svelte": "^2.37.0",
|
||||
"publint": "^0.2.7",
|
||||
"svelte": "^4.2.14",
|
||||
"svelte": "^4.2.15",
|
||||
"svelte-check": "^3.6.9",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.4.5",
|
||||
|
@@ -5,7 +5,6 @@
|
||||
import Select from "$lib/elements/Select.svelte";
|
||||
|
||||
import type { NodeInput } from "@nodes/types";
|
||||
import Slider from "./elements/Slider.svelte";
|
||||
|
||||
export let input: NodeInput;
|
||||
export let value: any;
|
||||
@@ -13,11 +12,7 @@
|
||||
</script>
|
||||
|
||||
{#if input.type === "float"}
|
||||
{#if input?.element === "slider"}
|
||||
<Slider {id} bind:value />
|
||||
{:else}
|
||||
<Float {id} bind:value />
|
||||
{/if}
|
||||
<Float {id} bind:value />
|
||||
{:else if input.type === "integer"}
|
||||
<Integer {id} bind:value />
|
||||
{:else if input.type === "boolean"}
|
||||
|
68
packages/ui/src/lib/app.css
Normal file
68
packages/ui/src/lib/app.css
Normal file
@@ -0,0 +1,68 @@
|
||||
/* fira-code-300 - latin */
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Fira Code';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('/fonts/fira-code-v22-latin-300.woff2') format('woff2');
|
||||
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
|
||||
/* fira-code-600 - latin */
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Fira Code';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('/fonts/fira-code-v22-latin-600.woff2') format('woff2');
|
||||
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
|
||||
:root {
|
||||
--font-family: 'Fira Code', monospace;
|
||||
font-family: var(--font-family);
|
||||
|
||||
/* Spacing */
|
||||
--spacing-xs: 4px;
|
||||
/* Extra small spacing */
|
||||
--spacing-sm: 8px;
|
||||
/* Small spacing */
|
||||
--spacing-md: 16px;
|
||||
/* Medium spacing */
|
||||
--spacing-lg: 24px;
|
||||
/* Large spacing */
|
||||
--spacing-xl: 32px;
|
||||
/* Extra large spacing */
|
||||
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
|
||||
/* Secondary color */
|
||||
--secondary-color: #6c757d;
|
||||
/* Background color */
|
||||
--background-color-lighter: #202020;
|
||||
--background-color: #151515;
|
||||
--background-color-darker: #101010;
|
||||
--text-color: #aeaeae;
|
||||
|
||||
background-color: var(--background-color-darker);
|
||||
}
|
||||
|
||||
body.theme-catppuccin {
|
||||
--text-color: #CDD6F4;
|
||||
--background-color-lighter: #313244;
|
||||
--background-color: #1E1E2E;
|
||||
--background-color-darker: #11111b;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* canvas { */
|
||||
/* display: none !important; */
|
||||
/* } */
|
@@ -1,20 +1,172 @@
|
||||
<script lang="ts">
|
||||
export let value: number = 0;
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { getBoundingValue } from '../helpers/getBoundingValue';
|
||||
|
||||
export let value = 0.5;
|
||||
export let step = 0.01;
|
||||
export let min = 0;
|
||||
export let max = 10;
|
||||
export let step = 0.1;
|
||||
export let id: string;
|
||||
export let max = 1;
|
||||
export let id = "";
|
||||
|
||||
function strip(input: number) {
|
||||
return +parseFloat(input + '').toPrecision(2);
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let inputEl: HTMLInputElement;
|
||||
|
||||
$: if ((value || 0).toString().length > 5) {
|
||||
value = strip(value || 0);
|
||||
}
|
||||
$: value !== undefined && handleChange();
|
||||
let oldValue: number;
|
||||
function handleChange() {
|
||||
if (value === oldValue) return;
|
||||
oldValue = value;
|
||||
dispatch('change', parseFloat(value + ''));
|
||||
}
|
||||
|
||||
$: width = Number.isFinite(value)
|
||||
? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px'
|
||||
: '20px';
|
||||
|
||||
let isMouseDown = false;
|
||||
/* let downX = 0; */
|
||||
/* let downY = 0; */
|
||||
let downV = 0;
|
||||
let vx = 0;
|
||||
/* let vy = 0; */
|
||||
let rect: DOMRect;
|
||||
|
||||
function handleMouseDown(ev: MouseEvent) {
|
||||
ev.preventDefault();
|
||||
|
||||
isMouseDown = true;
|
||||
|
||||
downV = value;
|
||||
/* downX = ev.clientX; */
|
||||
/* downY = ev.clientY; */
|
||||
rect = inputEl.getBoundingClientRect();
|
||||
|
||||
window.removeEventListener('mousemove', handleMouseMove);
|
||||
window.addEventListener('mousemove', handleMouseMove);
|
||||
window.addEventListener('mouseup', handleMouseUp);
|
||||
document.body.style.cursor = 'ew-resize';
|
||||
}
|
||||
|
||||
function handleMouseUp() {
|
||||
isMouseDown = false;
|
||||
|
||||
if (downV === value) {
|
||||
inputEl.focus();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (value >= 0) {
|
||||
max = getBoundingValue(value);
|
||||
min = 0;
|
||||
} else {
|
||||
min = getBoundingValue(value);
|
||||
max = 0;
|
||||
}
|
||||
}, 500);
|
||||
|
||||
document.body.style.cursor = 'unset';
|
||||
window.removeEventListener('mouseup', handleMouseUp);
|
||||
window.removeEventListener('mousemove', handleMouseMove);
|
||||
}
|
||||
|
||||
function handleKeyDown(ev: KeyboardEvent) {
|
||||
if (ev.key === 'Escape' || ev.key === 'Enter') {
|
||||
handleMouseUp();
|
||||
inputEl.blur();
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseMove(ev: MouseEvent) {
|
||||
vx = (ev.clientX - rect.left) / rect.width;
|
||||
/* vy = ev.clientY - downY; */
|
||||
|
||||
if (ev.ctrlKey) {
|
||||
let v = min + (max - min) * vx;
|
||||
value = v;
|
||||
} else {
|
||||
value = Math.max(Math.min(min + (max - min) * vx, max), min);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<input {id} type="number" bind:value {min} {max} {step} />
|
||||
<div class="component-wrapper" class:is-down={isMouseDown}>
|
||||
<span class="overlay" style={`width: ${((value - min) / (max - min)) * 100}%`} />
|
||||
<input
|
||||
bind:value
|
||||
bind:this={inputEl}
|
||||
{step}
|
||||
{max}
|
||||
{min}
|
||||
on:keydown={handleKeyDown}
|
||||
on:mousedown={handleMouseDown}
|
||||
on:mouseup={handleMouseUp}
|
||||
type="number"
|
||||
style={`width:${width};`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
input {
|
||||
background: var(--background-color-lighter);
|
||||
color: var(--text-color);
|
||||
.component-wrapper {
|
||||
position: relative;
|
||||
background-color: var(--background-color-lighter, #4b4b4b);
|
||||
border-radius: 2px;
|
||||
user-select: none;
|
||||
transition: box-shadow 0.3s ease;
|
||||
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.2);
|
||||
outline: none !important;
|
||||
overflow: hidden;
|
||||
border-radius: var(--border-radius, 2px);
|
||||
}
|
||||
|
||||
input[type='number']::-webkit-inner-spin-button,
|
||||
input[type='number']::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
input[type='number'] {
|
||||
box-sizing: border-box;
|
||||
-webkit-appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
font-family: var(--font-family);
|
||||
padding: 0.8em 1em;
|
||||
border-radius: 5px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
cursor: pointer;
|
||||
color: var(--text-color);
|
||||
background-color: transparent;
|
||||
padding: var(--padding, 6px);
|
||||
font-size: 1em;
|
||||
padding-inline: 10px;
|
||||
text-align: center;
|
||||
border: none;
|
||||
border-style: none;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.is-down > input {
|
||||
cursor: ew-resize !important;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
background-color: var(--text-color);
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.is-down > .overlay {
|
||||
transition: none !important;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,9 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let value: number = 0;
|
||||
export let min = 0;
|
||||
export let max = 10;
|
||||
export let step = 0.1;
|
||||
export let id: string;
|
||||
</script>
|
||||
|
||||
<input type="range" {id} bind:value {min} {max} {step} />
|
13
packages/ui/src/lib/helpers/getBoundingValue.ts
Normal file
13
packages/ui/src/lib/helpers/getBoundingValue.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export function getBoundingValue(_v: number) {
|
||||
const v = Math.abs(_v);
|
||||
|
||||
let level = 1;
|
||||
const levels = [1, 2, 4, 10, 20, 50, 100, 200, 300, 400, 500, 1000];
|
||||
|
||||
for (const l of levels) {
|
||||
level = l;
|
||||
if (l >= v) break;
|
||||
}
|
||||
|
||||
return _v >= 0 ? level : -level;
|
||||
}
|
@@ -1,3 +1,7 @@
|
||||
<h1>Welcome to your library project</h1>
|
||||
<p>Create your package using @sveltejs/package and preview/showcase your work with SvelteKit</p>
|
||||
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
|
||||
<script lang="ts">
|
||||
import "$lib/app.css";
|
||||
import Slider from "$lib/elements/Float.svelte";
|
||||
</script>
|
||||
|
||||
<Slider id="asd" />
|
||||
|
||||
|
BIN
packages/ui/static/fonts/fira-code-v22-latin-300.woff2
Normal file
BIN
packages/ui/static/fonts/fira-code-v22-latin-300.woff2
Normal file
Binary file not shown.
BIN
packages/ui/static/fonts/fira-code-v22-latin-600.woff2
Normal file
BIN
packages/ui/static/fonts/fira-code-v22-latin-600.woff2
Normal file
Binary file not shown.
Reference in New Issue
Block a user