@aaqu/fromcubes-portal-react 0.1.0-alpha.29 → 0.1.0-alpha.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/examples/Live Chart.json +0 -2
- package/examples/PixiJS Sprites.json +0 -4
- package/examples/Sensor Portal.json +0 -4
- package/examples/Shared Components.json +0 -10
- package/examples/Three.js Scene.json +0 -4
- package/examples/Utility Hooks.json +0 -2
- package/examples/WebGPU Shader.json +0 -4
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
# @aaqu/fromcubes-portal-react
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@aaqu/fromcubes-portal-react)
|
|
4
|
+
[](https://www.npmjs.com/package/@aaqu/fromcubes-portal-react)
|
|
5
|
+
[](https://www.npmjs.com/package/@aaqu/fromcubes-portal-react)
|
|
6
|
+
[](https://nodered.org)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
**📖 Documentation — step-by-step guide with screenshots:** [aaqu.github.io/fromcubes-portal-react](https://aaqu.github.io/fromcubes-portal-react/) — install → import → edit JSX → live dashboard, on one example.
|
|
10
|
+
|
|
3
11
|
> **⚠️ Alpha Module** — This project is in early development. Expect breaking changes. Test on a clean Node-RED instance.
|
|
4
12
|
|
|
5
13
|
A Node-RED node that turns any `/fromcubes/<sub-path>` URL into a React page. Write JSX in the editor, deploy, open the URL — your component talks to the flow over WebSocket. No build step, no browser compiler. All portal pages are served under the hardcoded `/fromcubes/` prefix so every node cleanly coexists under one URL tree.
|
|
6
14
|
|
|
7
15
|
For internals, plugin authoring, and the deploy pipeline see [README-DEV.md](./README-DEV.md).
|
|
16
|
+
Want an AI model to generate flows for you? Paste [AI-GUIDE.md](./AI-GUIDE.md) into it — a self-contained authoring contract (node types, flow-JSON fields, runtime API, rules, skeleton).
|
|
8
17
|
|
|
9
18
|
[](https://ko-fi.com/L4L01UOFRG)
|
|
10
19
|
|
|
@@ -88,6 +97,7 @@ The opt-out is page-wide — the strictest call wins. If any component on the pa
|
|
|
88
97
|
| Page Title | Browser tab title |
|
|
89
98
|
| npm Packages | Comma-separated, e.g. `d3, three, @react-three/fiber` |
|
|
90
99
|
| Portal Auth | Enable portal user header extraction (see Multi-user) |
|
|
100
|
+
| Show WS status | Small "fromcubes • connected/disconnected" badge in the page's bottom-right corner (off by default) |
|
|
91
101
|
| Head HTML | Extra trusted-author `<head>` tags (CDN, fonts, CSS, scripts). Runs in the public portal page. |
|
|
92
102
|
| Code Editor | Monaco with JSX — must define `<App />` |
|
|
93
103
|
|
package/examples/Live Chart.json
CHANGED
|
@@ -21,8 +21,6 @@
|
|
|
21
21
|
"z": "fc-chart-flow",
|
|
22
22
|
"name": "LineChart",
|
|
23
23
|
"compName": "LineChart",
|
|
24
|
-
"compInputs": "data,labelKey,datasets,title,height",
|
|
25
|
-
"compOutputs": "",
|
|
26
24
|
"compCode": "import ChartJS from 'chart.js/auto';\n\nfunction LineChart({ data = [], labelKey = 'time', datasets = [], title = '', height = 'h-64' }) {\n const canvasRef = useRef(null);\n const chartRef = useRef(null);\n\n useEffect(() => {\n if (!canvasRef.current || data.length === 0) return;\n\n if (chartRef.current) {\n const chart = chartRef.current;\n chart.data.labels = data.map(p => p[labelKey]);\n datasets.forEach((ds, i) => {\n chart.data.datasets[i].data = data.map(p => p[ds.key]);\n });\n chart.update('none');\n return;\n }\n\n\n chartRef.current = new ChartJS(canvasRef.current, {\n type: 'line',\n data: {\n labels: data.map(p => p[labelKey]),\n datasets: datasets.map(ds => ({\n label: ds.label,\n data: data.map(p => p[ds.key]),\n borderColor: ds.color,\n backgroundColor: ds.color + '18',\n tension: 0.35,\n fill: true,\n pointRadius: 2,\n borderWidth: 2\n }))\n },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n animation: false,\n plugins: {\n legend: { labels: { color: '#a1a1aa', usePointStyle: true, pointStyle: 'circle', padding: 16 } }\n },\n scales: {\n x: { ticks: { color: '#52525b', font: { size: 10 } }, grid: { color: '#27272a' } },\n y: { ticks: { color: '#52525b', font: { size: 10 } }, grid: { color: '#27272a' } }\n }\n }\n });\n\n return () => {\n if (chartRef.current) {\n chartRef.current.destroy();\n chartRef.current = null;\n }\n };\n }, [data, datasets]);\n\n return (\n <div className=\"bg-zinc-900/50 rounded-xl border border-zinc-800/50 p-5\">\n {title && <h3 className=\"text-sm font-medium text-zinc-300 mb-3\">{title}</h3>}\n <div className={height}>\n <canvas ref={canvasRef} />\n </div>\n </div>\n );\n}",
|
|
27
25
|
"x": 380,
|
|
28
26
|
"y": 80,
|
|
@@ -21,8 +21,6 @@
|
|
|
21
21
|
"z": "fc-pixi-flow",
|
|
22
22
|
"compName": "ClickableSprite",
|
|
23
23
|
"compCode": "import { Assets, Texture } from 'pixi.js';\nimport { useTick } from '@pixi/react';\n\nfunction ClickableSprite({ id, x, y, tint, onClick }) {\n const ref = useRef(null);\n const [texture, setTexture] = useState(Texture.EMPTY);\n const [hovered, setHovered] = useState(false);\n const clicks = useRef(0);\n const time = useRef(Math.random() * Math.PI * 2);\n\n useEffect(() => {\n if (texture === Texture.EMPTY) {\n Assets.load('https://pixijs.com/assets/bunny.png').then(setTexture);\n }\n }, [texture]);\n\n useTick((t) => {\n if (!ref.current) return;\n time.current += t.deltaTime * 0.03;\n ref.current.rotation = Math.sin(time.current) * 0.15;\n });\n\n return (\n <pixiSprite\n ref={ref}\n texture={texture}\n anchor={0.5}\n x={x}\n y={y}\n tint={tint}\n scale={hovered ? 2.2 : 1.8}\n eventMode=\"static\"\n cursor=\"pointer\"\n onPointerOver={() => setHovered(true)}\n onPointerOut={() => setHovered(false)}\n onClick={() => {\n clicks.current += 1;\n onClick({ id, x, y, clicks: clicks.current });\n }}\n />\n );\n}",
|
|
24
|
-
"compInputs": "id,x,y,tint,onClick",
|
|
25
|
-
"compOutputs": "",
|
|
26
24
|
"x": 160,
|
|
27
25
|
"y": 300,
|
|
28
26
|
"wires": []
|
|
@@ -33,8 +31,6 @@
|
|
|
33
31
|
"z": "fc-pixi-flow",
|
|
34
32
|
"compName": "StatusBar",
|
|
35
33
|
"compCode": "function StatusBar({ spriteCount, totalClicks, lastClicked }) {\n return (\n <div className=\"px-6 py-4 bg-zinc-900/50 border-t border-zinc-800/50 flex items-center gap-6 flex-wrap\">\n <Stat label=\"Sprites\">\n <span className=\"text-sm font-mono text-cyan-400\">{spriteCount}</span>\n </Stat>\n <Stat label=\"Total clicks\">\n <span className=\"text-sm font-mono text-emerald-400\">{totalClicks}</span>\n </Stat>\n <Stat label=\"Last clicked\">\n <span className=\"text-sm font-mono text-zinc-300\">{lastClicked || '\\u2014'}</span>\n </Stat>\n </div>\n );\n}",
|
|
36
|
-
"compInputs": "spriteCount,totalClicks,lastClicked",
|
|
37
|
-
"compOutputs": "",
|
|
38
34
|
"x": 360,
|
|
39
35
|
"y": 300,
|
|
40
36
|
"wires": []
|
|
@@ -58,8 +58,6 @@
|
|
|
58
58
|
"name": "Gauge",
|
|
59
59
|
"compName": "Gauge",
|
|
60
60
|
"compCode": "function Gauge({ value = 0, min = 0, max = 100, label = '', unit = '' }) {\n const pct = Math.min(100, Math.max(0, ((value - min) / (max - min)) * 100));\n const color = pct > 80 ? 'text-red-400' : pct > 50 ? 'text-amber-400' : 'text-green-400';\n const bg = pct > 80 ? 'from-red-500' : pct > 50 ? 'from-amber-500' : 'from-green-500';\n return (\n <div className=\"flex flex-col items-center p-4\">\n <div className=\"text-xs text-zinc-500 uppercase tracking-wider mb-2\">{label}</div>\n <div className=\"relative w-28 h-28 rounded-full bg-zinc-900 flex items-center justify-center\">\n <svg className=\"absolute inset-0 w-full h-full -rotate-90\" viewBox=\"0 0 100 100\">\n <circle cx=\"50\" cy=\"50\" r=\"42\" fill=\"none\" strokeWidth=\"6\" className=\"stroke-zinc-800\" />\n <circle cx=\"50\" cy=\"50\" r=\"42\" fill=\"none\" strokeWidth=\"6\"\n className={`stroke-current ${color}`}\n strokeDasharray={`${pct * 2.64} 264`}\n strokeLinecap=\"round\" />\n </svg>\n <div className={`text-2xl font-bold ${color}`}>\n {Math.round(value)}<span className=\"text-sm ml-0.5\">{unit}</span>\n </div>\n </div>\n </div>\n );\n}",
|
|
61
|
-
"compInputs": "value,min,max,label,unit",
|
|
62
|
-
"compOutputs": "",
|
|
63
61
|
"x": 160,
|
|
64
62
|
"y": 280,
|
|
65
63
|
"wires": []
|
|
@@ -72,8 +70,6 @@
|
|
|
72
70
|
"subPath": "sensors",
|
|
73
71
|
"pageTitle": "Sensors",
|
|
74
72
|
"customHead": "",
|
|
75
|
-
"inputSchema": "[{\"name\":\"temp\",\"type\":\"number\"},{\"name\":\"humidity\",\"type\":\"number\"},{\"name\":\"pressure\",\"type\":\"number\"},{\"name\":\"ts\",\"type\":\"number\"}]",
|
|
76
|
-
"outputSchema": "[{\"name\":\"action\",\"type\":\"string\"}]",
|
|
77
73
|
"componentCode": "function App() {\n const { data, send } = useNodeRed();\n const s = data || { temp: 0, humidity: 0, pressure: 0 };\n\n return (\n <Page>\n <Header title=\"Sensor Portal\" subtitle=\"live sensor data\" />\n <div className=\"flex-1 flex flex-col items-center justify-center p-8\">\n <div className=\"flex gap-10 flex-wrap justify-center\">\n <Gauge value={s.temp} min={-10} max={50} label=\"Temperature\" unit=\"°C\" />\n <Gauge value={s.humidity} min={0} max={100} label=\"Humidity\" unit=\"%\" />\n <Gauge value={s.pressure} min={950} max={1050} label=\"Pressure\" unit=\"hPa\" />\n </div>\n <div className=\"mt-10 flex gap-3 items-center\">\n <Button onClick={() => send({ action: 'refresh' })}>Refresh</Button>\n <ValueBadge label=\"Last\" value={s.ts ? new Date(s.ts).toLocaleTimeString() : null} />\n </div>\n </div>\n </Page>\n );\n}",
|
|
78
74
|
"x": 560,
|
|
79
75
|
"y": 160,
|
|
@@ -21,8 +21,6 @@
|
|
|
21
21
|
"z": "fc-shared-flow",
|
|
22
22
|
"compName": "Header",
|
|
23
23
|
"compCode": "function Header({ title = 'fromcubes', subtitle = '', children }) {\n return (\n <div className=\"flex items-center justify-between px-6 py-4\">\n <div>\n <h1 className=\"text-xl font-semibold text-zinc-100 tracking-tight\">{title}</h1>\n {subtitle && <p className=\"text-xs text-zinc-600 mt-0.5\">{subtitle}</p>}\n </div>\n <div className=\"flex items-center gap-2\">\n {children || (\n <>\n <span className=\"inline-block w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse\" />\n <span className=\"text-xs text-zinc-500\">streaming</span>\n </>\n )}\n </div>\n </div>\n );\n}",
|
|
24
|
-
"compInputs": "title,subtitle,children",
|
|
25
|
-
"compOutputs": "",
|
|
26
24
|
"x": 160,
|
|
27
25
|
"y": 160,
|
|
28
26
|
"wires": []
|
|
@@ -33,8 +31,6 @@
|
|
|
33
31
|
"z": "fc-shared-flow",
|
|
34
32
|
"compName": "Stat",
|
|
35
33
|
"compCode": "function Stat({ label, children }) {\n return (\n <div className=\"flex items-center gap-3\">\n <span className=\"text-xs text-zinc-500\">{label}</span>\n {children}\n </div>\n );\n}",
|
|
36
|
-
"compInputs": "label,children",
|
|
37
|
-
"compOutputs": "",
|
|
38
34
|
"x": 360,
|
|
39
35
|
"y": 160,
|
|
40
36
|
"wires": []
|
|
@@ -45,8 +41,6 @@
|
|
|
45
41
|
"z": "fc-shared-flow",
|
|
46
42
|
"compName": "Button",
|
|
47
43
|
"compCode": "function Button({ onClick, children, variant = 'primary', className = '' }) {\n const base = 'px-4 py-2 rounded-lg font-medium transition-colors';\n const variants = {\n primary: 'bg-blue-600 text-white hover:bg-blue-500',\n secondary: 'bg-zinc-700 text-zinc-200 hover:bg-zinc-600',\n danger: 'bg-red-600 text-white hover:bg-red-500'\n };\n return (\n <button\n className={`${base} ${variants[variant] || variants.primary} ${className}`}\n onClick={onClick}\n >{children}</button>\n );\n}",
|
|
48
|
-
"compInputs": "onClick,children,variant,className",
|
|
49
|
-
"compOutputs": "",
|
|
50
44
|
"x": 560,
|
|
51
45
|
"y": 160,
|
|
52
46
|
"wires": []
|
|
@@ -57,8 +51,6 @@
|
|
|
57
51
|
"z": "fc-shared-flow",
|
|
58
52
|
"compName": "ValueBadge",
|
|
59
53
|
"compCode": "function ValueBadge({ label, value, className = '' }) {\n if (value == null) return null;\n return (\n <span className={`text-zinc-600 text-xs ${className}`}>\n {label && <>{label}: </>}{value}\n </span>\n );\n}",
|
|
60
|
-
"compInputs": "label,value,className",
|
|
61
|
-
"compOutputs": "",
|
|
62
54
|
"x": 760,
|
|
63
55
|
"y": 160,
|
|
64
56
|
"wires": []
|
|
@@ -69,8 +61,6 @@
|
|
|
69
61
|
"z": "fc-shared-flow",
|
|
70
62
|
"compName": "Page",
|
|
71
63
|
"compCode": "function Page({ children, className = '' }) {\n return (\n <div className={`min-h-screen bg-zinc-950 text-zinc-100 flex flex-col ${className}`}>\n {children}\n </div>\n );\n}",
|
|
72
|
-
"compInputs": "children,className",
|
|
73
|
-
"compOutputs": "",
|
|
74
64
|
"x": 160,
|
|
75
65
|
"y": 240,
|
|
76
66
|
"wires": []
|
|
@@ -21,8 +21,6 @@
|
|
|
21
21
|
"z": "fc-three-flow",
|
|
22
22
|
"compName": "Box",
|
|
23
23
|
"compCode": "import { useFrame } from '@react-three/fiber';\n\nfunction Box({ position, color = 'orange', onToggle }) {\n const ref = useRef();\n const [hovered, hover] = useState(false);\n const [clicked, click] = useState(false);\n\n useFrame((state, delta) => (ref.current.rotation.x += delta));\n\n return (\n <mesh\n position={position}\n ref={ref}\n scale={clicked ? 1.5 : 1}\n onClick={() => {\n click(!clicked);\n if (onToggle) onToggle(!clicked);\n }}\n onPointerOver={(e) => (e.stopPropagation(), hover(true))}\n onPointerOut={() => hover(false)}\n >\n <boxGeometry args={[1, 1, 1]} />\n <meshStandardMaterial color={hovered ? 'hotpink' : color} />\n </mesh>\n );\n}",
|
|
24
|
-
"compInputs": "position,color,onToggle",
|
|
25
|
-
"compOutputs": "",
|
|
26
24
|
"x": 160,
|
|
27
25
|
"y": 300,
|
|
28
26
|
"wires": []
|
|
@@ -33,8 +31,6 @@
|
|
|
33
31
|
"z": "fc-three-flow",
|
|
34
32
|
"compName": "StatusBar",
|
|
35
33
|
"compCode": "function StatusBar({ leftColor, rightColor, clickCount }) {\n return (\n <div className=\"px-6 py-4 bg-zinc-900/50 border-t border-zinc-800/50 flex items-center gap-6 flex-wrap\">\n <Stat label=\"Left box\">\n <span className=\"w-4 h-4 rounded-full border border-zinc-700\" style={{ backgroundColor: leftColor }} />\n </Stat>\n <Stat label=\"Right box\">\n <span className=\"w-4 h-4 rounded-full border border-zinc-700\" style={{ backgroundColor: rightColor }} />\n </Stat>\n <Stat label=\"Clicks\">\n <span className=\"text-sm font-mono text-cyan-400\">{clickCount}</span>\n </Stat>\n </div>\n );\n}",
|
|
36
|
-
"compInputs": "leftColor,rightColor,clickCount",
|
|
37
|
-
"compOutputs": "",
|
|
38
34
|
"x": 360,
|
|
39
35
|
"y": 300,
|
|
40
36
|
"wires": []
|
|
@@ -69,8 +69,6 @@
|
|
|
69
69
|
"name": "Bar",
|
|
70
70
|
"compName": "Bar",
|
|
71
71
|
"compCode": "function Bar({ value = 0, label = '' }) {\n // Component uses `clamp` from the utility node — selective inclusion picks\n // up the dependency through the components' code, not just the user JSX.\n const pct = clamp(value, CLAMP_MIN, CLAMP_MAX);\n const color = pct > 80 ? 'bg-red-500' : pct > 50 ? 'bg-amber-500' : 'bg-emerald-500';\n return (\n <div className=\"flex items-center gap-3\">\n <div className=\"w-24 text-xs text-zinc-400 uppercase tracking-wider\">{label}</div>\n <div className=\"flex-1 h-3 bg-zinc-800 rounded-full overflow-hidden\">\n <div className={`h-full ${color} transition-all duration-150`} style={{ width: pct + '%' }} />\n </div>\n <div className=\"w-14 text-right font-mono text-sm tabular-nums text-zinc-200\">\n {value.toFixed(1)}\n </div>\n </div>\n );\n}",
|
|
72
|
-
"compInputs": "value,label",
|
|
73
|
-
"compOutputs": "",
|
|
74
72
|
"x": 360,
|
|
75
73
|
"y": 280,
|
|
76
74
|
"wires": []
|
|
@@ -21,8 +21,6 @@
|
|
|
21
21
|
"z": "fc-webgpu-flow",
|
|
22
22
|
"compName": "TSLCanvas",
|
|
23
23
|
"compCode": "import { AmbientLight, Color, Mesh, MeshStandardNodeMaterial, PerspectiveCamera, PointLight, Scene, Timer, TorusKnotGeometry, WebGPURenderer } from 'three/webgpu';\nimport { uniform, sin, cos, time, uv, vec3, mix, float, positionLocal, normalLocal } from 'three/tsl';\n\nfunction TSLCanvas({ params }) {\n const mountRef = useRef(null);\n const stateRef = useRef(null);\n\n useEffect(() => {\n const el = mountRef.current;\n const w = el.clientWidth;\n const h = el.clientHeight;\n\n const scene = new Scene();\n const camera = new PerspectiveCamera(50, w / h, 0.1, 100);\n camera.position.z = 4;\n\n const renderer = new WebGPURenderer({ antialias: true });\n renderer.setSize(w, h);\n renderer.setPixelRatio(window.devicePixelRatio);\n el.appendChild(renderer.domElement);\n\n scene.add(new AmbientLight(0xffffff, 0.5));\n const point = new PointLight(0xffffff, 1.5, 100);\n point.position.set(5, 5, 5);\n scene.add(point);\n\n const colorAU = uniform(new Color(0x22d3ee));\n const colorBU = uniform(new Color(0xa78bfa));\n\n const t = time;\n const uvNode = uv();\n const wave = sin(uvNode.x.mul(6.28).add(t)).mul(0.5).add(0.5);\n const pulse = cos(t.mul(0.7)).mul(0.5).add(0.5);\n const blend = mix(vec3(colorAU), vec3(colorBU), wave.mul(pulse));\n\n const mat = new MeshStandardNodeMaterial();\n mat.colorNode = blend;\n mat.roughnessNode = float(0.3);\n mat.metalnessNode = float(0.7);\n\n const displaceAmount = sin(positionLocal.y.mul(4.0).add(t)).mul(0.08);\n mat.positionNode = positionLocal.add(normalLocal.mul(displaceAmount));\n\n const geo = new TorusKnotGeometry(1, 0.35, 128, 32);\n const mesh = new Mesh(geo, mat);\n scene.add(mesh);\n\n const targetA = new Color(0x22d3ee);\n const targetB = new Color(0xa78bfa);\n const st = { mesh, scene, camera, renderer, colorAU, colorBU, targetA, targetB };\n stateRef.current = st;\n\n let raf;\n const timer = new Timer();\n function animate() {\n raf = requestAnimationFrame(animate);\n timer.update();\n const dt = timer.getDelta();\n mesh.rotation.x += 0.3 * dt;\n mesh.rotation.y += 0.6 * dt;\n st.colorAU.value.lerp(st.targetA, Math.min(1, 2.5 * dt));\n st.colorBU.value.lerp(st.targetB, Math.min(1, 2.5 * dt));\n renderer.render(scene, camera);\n }\n\n renderer.init().then(() => animate());\n\n const onResize = () => {\n const w2 = el.clientWidth;\n const h2 = el.clientHeight;\n camera.aspect = w2 / h2;\n camera.updateProjectionMatrix();\n renderer.setSize(w2, h2);\n };\n window.addEventListener('resize', onResize);\n\n return () => {\n cancelAnimationFrame(raf);\n window.removeEventListener('resize', onResize);\n renderer.dispose();\n geo.dispose();\n mat.dispose();\n el.removeChild(renderer.domElement);\n };\n }, []);\n\n useEffect(() => {\n const s = stateRef.current;\n if (!s) return;\n s.targetA.set(params.colorA);\n s.targetB.set(params.colorB);\n }, [params.colorA, params.colorB]);\n\n return <div ref={mountRef} className=\"flex-1\" />;\n}",
|
|
24
|
-
"compInputs": "params",
|
|
25
|
-
"compOutputs": "",
|
|
26
24
|
"x": 160,
|
|
27
25
|
"y": 300,
|
|
28
26
|
"wires": []
|
|
@@ -33,8 +31,6 @@
|
|
|
33
31
|
"z": "fc-webgpu-flow",
|
|
34
32
|
"compName": "TSLStatusBar",
|
|
35
33
|
"compCode": "function TSLStatusBar({ params }) {\n return (\n <div className=\"px-6 py-4 bg-zinc-900/50 border-t border-zinc-800/50 flex items-center gap-6 flex-wrap\">\n <Stat label=\"Color A\">\n <span className=\"w-4 h-4 rounded-full border border-zinc-700\" style={{backgroundColor: params.colorA}} />\n </Stat>\n <Stat label=\"Color B\">\n <span className=\"w-4 h-4 rounded-full border border-zinc-700\" style={{backgroundColor: params.colorB}} />\n </Stat>\n <Stat label=\"Backend\">\n <span className=\"text-xs font-mono px-2 py-0.5 rounded bg-violet-500/20 text-violet-300\">WebGPU / TSL</span>\n </Stat>\n </div>\n );\n}",
|
|
36
|
-
"compInputs": "params",
|
|
37
|
-
"compOutputs": "",
|
|
38
34
|
"x": 360,
|
|
39
35
|
"y": 300,
|
|
40
36
|
"wires": []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aaqu/fromcubes-portal-react",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.30",
|
|
4
4
|
"description": "Fromcubes Portal - React for Node-RED with Tailwind CSS and auto complete",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"keywords": [
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"nodes/",
|
|
24
24
|
"examples/",
|
|
25
25
|
"README.md",
|
|
26
|
+
"AI-GUIDE.md",
|
|
26
27
|
"LICENSE"
|
|
27
28
|
],
|
|
28
29
|
"engines": {
|