@aaqu/fromcubes-portal-react 0.1.0-alpha.12 → 0.1.0-alpha.13

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.
@@ -0,0 +1,87 @@
1
+ [
2
+ {
3
+ "id": "fc-three-flow",
4
+ "type": "tab",
5
+ "label": "Three.js Demo",
6
+ "disabled": false
7
+ },
8
+ {
9
+ "id": "fc-three-comp-box",
10
+ "type": "fc-portal-component",
11
+ "z": "fc-three-flow",
12
+ "compName": "Box",
13
+ "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}",
14
+ "compInputs": "position,color,onToggle",
15
+ "compOutputs": "",
16
+ "x": 160,
17
+ "y": 300,
18
+ "wires": []
19
+ },
20
+ {
21
+ "id": "fc-three-comp-statusbar",
22
+ "type": "fc-portal-component",
23
+ "z": "fc-three-flow",
24
+ "compName": "StatusBar",
25
+ "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}",
26
+ "compInputs": "leftColor,rightColor,clickCount",
27
+ "compOutputs": "",
28
+ "x": 360,
29
+ "y": 300,
30
+ "wires": []
31
+ },
32
+ {
33
+ "id": "fc-three-inject",
34
+ "type": "inject",
35
+ "z": "fc-three-flow",
36
+ "name": "Tick",
37
+ "props": [{ "p": "payload" }],
38
+ "repeat": "3",
39
+ "payload": "{}",
40
+ "payloadType": "json",
41
+ "x": 160,
42
+ "y": 160,
43
+ "wires": [["fc-three-func"]]
44
+ },
45
+ {
46
+ "id": "fc-three-func",
47
+ "type": "function",
48
+ "z": "fc-three-flow",
49
+ "name": "Random colors",
50
+ "func": "const rnd = () => '#' + Math.floor(Math.random()*16777215).toString(16).padStart(6,'0');\nmsg.payload = {\n leftColor: rnd(),\n rightColor: rnd()\n};\nreturn msg;",
51
+ "outputs": 1,
52
+ "x": 360,
53
+ "y": 160,
54
+ "wires": [["fc-three-portal"]]
55
+ },
56
+ {
57
+ "id": "fc-three-portal",
58
+ "type": "portal-react",
59
+ "z": "fc-three-flow",
60
+ "name": "3D Portal",
61
+ "endpoint": "/fromcubes/3d",
62
+ "pageTitle": "fromcubes 3D",
63
+ "customHead": "",
64
+ "portalAuth": false,
65
+ "showWsStatus": false,
66
+ "libs": [
67
+ { "module": "three", "var": "THREE" },
68
+ { "module": "@react-three/fiber", "var": "R3F" },
69
+ { "module": "@react-three/drei", "var": "Drei" }
70
+ ],
71
+ "componentCode": "import { Canvas } from '@react-three/fiber';\nimport { OrbitControls } from '@react-three/drei';\n\nfunction App() {\n const { data, send } = useNodeRed();\n const colors = data || { leftColor: 'orange', rightColor: 'cyan' };\n const [clickCount, setClickCount] = useState(0);\n\n const handleToggle = useCallback((clicked) => {\n setClickCount(prev => prev + 1);\n send({ action: clicked ? 'clicked' : 'unclicked', clicks: clickCount + 1 }, '3d/interact');\n }, [send, clickCount]);\n\n return (\n <div className=\"h-screen bg-zinc-950 text-zinc-100 flex flex-col\">\n <Header title=\"fromcubes 3D\" subtitle=\"React Three Fiber + live Node-RED data\" />\n <div className=\"flex-1 overflow-hidden\">\n <Canvas style={{ width: '100%', height: '100%' }}>\n <ambientLight intensity={Math.PI / 2} />\n <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />\n <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />\n <Box position={[-1.2, 0, 0]} color={colors.leftColor} onToggle={handleToggle} />\n <Box position={[1.2, 0, 0]} color={colors.rightColor} onToggle={handleToggle} />\n <OrbitControls />\n </Canvas>\n </div>\n <StatusBar leftColor={colors.leftColor} rightColor={colors.rightColor} clickCount={clickCount} />\n </div>\n );\n}",
72
+ "x": 560,
73
+ "y": 160,
74
+ "wires": [["fc-three-debug"]]
75
+ },
76
+ {
77
+ "id": "fc-three-debug",
78
+ "type": "debug",
79
+ "z": "fc-three-flow",
80
+ "name": "3D output",
81
+ "active": true,
82
+ "tosidebar": true,
83
+ "x": 750,
84
+ "y": 160,
85
+ "wires": []
86
+ }
87
+ ]
@@ -0,0 +1,86 @@
1
+ [
2
+ {
3
+ "id": "fc-pixi-flow",
4
+ "type": "tab",
5
+ "label": "PixiJS Demo",
6
+ "disabled": false
7
+ },
8
+ {
9
+ "id": "fc-pixi-comp-clicksprite",
10
+ "type": "fc-portal-component",
11
+ "z": "fc-pixi-flow",
12
+ "compName": "ClickableSprite",
13
+ "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}",
14
+ "compInputs": "id,x,y,tint,onClick",
15
+ "compOutputs": "",
16
+ "x": 160,
17
+ "y": 300,
18
+ "wires": []
19
+ },
20
+ {
21
+ "id": "fc-pixi-comp-statusbar",
22
+ "type": "fc-portal-component",
23
+ "z": "fc-pixi-flow",
24
+ "compName": "StatusBar",
25
+ "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}",
26
+ "compInputs": "spriteCount,totalClicks,lastClicked",
27
+ "compOutputs": "",
28
+ "x": 360,
29
+ "y": 300,
30
+ "wires": []
31
+ },
32
+ {
33
+ "id": "fc-pixi-inject",
34
+ "type": "inject",
35
+ "z": "fc-pixi-flow",
36
+ "name": "Tick",
37
+ "props": [{ "p": "payload" }],
38
+ "repeat": "5",
39
+ "payload": "{}",
40
+ "payloadType": "json",
41
+ "x": 160,
42
+ "y": 160,
43
+ "wires": [["fc-pixi-func"]]
44
+ },
45
+ {
46
+ "id": "fc-pixi-func",
47
+ "type": "function",
48
+ "z": "fc-pixi-flow",
49
+ "name": "Random tint",
50
+ "func": "const tints = [0x22d3ee, 0xa78bfa, 0x34d399, 0xfbbf24, 0xf87171, 0x60a5fa, 0xfb923c, 0xe879f9];\nmsg.payload = {\n tint: tints[Math.floor(Math.random() * tints.length)]\n};\nreturn msg;",
51
+ "outputs": 1,
52
+ "x": 360,
53
+ "y": 160,
54
+ "wires": [["fc-pixi-portal"]]
55
+ },
56
+ {
57
+ "id": "fc-pixi-portal",
58
+ "type": "portal-react",
59
+ "z": "fc-pixi-flow",
60
+ "name": "Pixi Portal",
61
+ "endpoint": "/fromcubes/pixi",
62
+ "pageTitle": "fromcubes Pixi",
63
+ "customHead": "",
64
+ "portalAuth": false,
65
+ "showWsStatus": false,
66
+ "libs": [
67
+ { "module": "pixi.js", "var": "PIXI" },
68
+ { "module": "@pixi/react", "var": "PixiReact" }
69
+ ],
70
+ "componentCode": "import { Sprite } from 'pixi.js';\nimport { Application, extend } from '@pixi/react';\nextend({ Sprite });\n\nfunction App() {\n const { data, send } = useNodeRed();\n const [sprites, setSprites] = useState([]);\n const [totalClicks, setTotalClicks] = useState(0);\n const [lastClicked, setLastClicked] = useState(null);\n const idRef = useRef(0);\n\n useEffect(() => {\n if (!data) return;\n setSprites(prev => {\n const next = [...prev, {\n id: idRef.current++,\n x: 40 + Math.random() * 520,\n y: 40 + Math.random() * 320,\n tint: data.tint || 0x22d3ee\n }];\n return next.length > 12 ? next.slice(-12) : next;\n });\n }, [data]);\n\n const handleClick = useCallback((info) => {\n setTotalClicks(c => c + 1);\n setLastClicked('#' + info.id);\n send({\n action: 'clicked',\n spriteId: info.id,\n x: Math.round(info.x),\n y: Math.round(info.y),\n spriteClicks: info.clicks,\n timestamp: Date.now()\n }, 'pixi/click');\n }, [send]);\n\n return (\n <Page>\n <Header title=\"fromcubes Pixi\" subtitle=\"click the bunnies\" />\n <div className=\"flex-1 flex items-center justify-center p-6\">\n <div className=\"rounded-xl overflow-hidden border border-zinc-800\">\n <Application width={600} height={400} background={0x18181b} antialias>\n {sprites.map(s => (\n <ClickableSprite key={s.id} {...s} onClick={handleClick} />\n ))}\n </Application>\n </div>\n </div>\n <StatusBar spriteCount={sprites.length} totalClicks={totalClicks} lastClicked={lastClicked} />\n </Page>\n );\n}",
71
+ "x": 560,
72
+ "y": 160,
73
+ "wires": [["fc-pixi-debug"]]
74
+ },
75
+ {
76
+ "id": "fc-pixi-debug",
77
+ "type": "debug",
78
+ "z": "fc-pixi-flow",
79
+ "name": "Pixi output",
80
+ "active": true,
81
+ "tosidebar": true,
82
+ "x": 750,
83
+ "y": 160,
84
+ "wires": []
85
+ }
86
+ ]
@@ -0,0 +1,85 @@
1
+ [
2
+ {
3
+ "id": "fc-webgpu-flow",
4
+ "type": "tab",
5
+ "label": "WebGPU TSL Demo",
6
+ "disabled": false
7
+ },
8
+ {
9
+ "id": "fc-webgpu-comp-canvas",
10
+ "type": "fc-portal-component",
11
+ "z": "fc-webgpu-flow",
12
+ "compName": "TSLCanvas",
13
+ "compCode": "import * as THREE 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 THREE.Scene();\n const camera = new THREE.PerspectiveCamera(50, w / h, 0.1, 100);\n camera.position.z = 4;\n\n const renderer = new THREE.WebGPURenderer({ antialias: true });\n renderer.setSize(w, h);\n renderer.setPixelRatio(window.devicePixelRatio);\n el.appendChild(renderer.domElement);\n\n scene.add(new THREE.AmbientLight(0xffffff, 0.5));\n const point = new THREE.PointLight(0xffffff, 1.5, 100);\n point.position.set(5, 5, 5);\n scene.add(point);\n\n const colorAU = uniform(new THREE.Color(0x22d3ee));\n const colorBU = uniform(new THREE.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 THREE.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 THREE.TorusKnotGeometry(1, 0.35, 128, 32);\n const mesh = new THREE.Mesh(geo, mat);\n scene.add(mesh);\n\n const targetA = new THREE.Color(0x22d3ee);\n const targetB = new THREE.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 THREE.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}",
14
+ "compInputs": "params",
15
+ "compOutputs": "",
16
+ "x": 160,
17
+ "y": 300,
18
+ "wires": []
19
+ },
20
+ {
21
+ "id": "fc-webgpu-comp-statusbar",
22
+ "type": "fc-portal-component",
23
+ "z": "fc-webgpu-flow",
24
+ "compName": "TSLStatusBar",
25
+ "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}",
26
+ "compInputs": "params",
27
+ "compOutputs": "",
28
+ "x": 360,
29
+ "y": 300,
30
+ "wires": []
31
+ },
32
+ {
33
+ "id": "fc-webgpu-inject",
34
+ "type": "inject",
35
+ "z": "fc-webgpu-flow",
36
+ "name": "Tick",
37
+ "props": [{ "p": "payload" }],
38
+ "repeat": "3",
39
+ "payload": "{}",
40
+ "payloadType": "json",
41
+ "x": 160,
42
+ "y": 160,
43
+ "wires": [["fc-webgpu-func"]]
44
+ },
45
+ {
46
+ "id": "fc-webgpu-func",
47
+ "type": "function",
48
+ "z": "fc-webgpu-flow",
49
+ "name": "Random TSL params",
50
+ "func": "const pairs = [\n ['#22d3ee','#a78bfa'],\n ['#a78bfa','#f97316'],\n ['#34d399','#e879f9'],\n ['#f97316','#60a5fa'],\n ['#f87171','#34d399'],\n ['#e879f9','#22d3ee'],\n ['#fbbf24','#f87171']\n];\nconst p = pairs[Math.floor(Math.random() * pairs.length)];\nmsg.payload = { colorA: p[0], colorB: p[1] };\nreturn msg;",
51
+ "outputs": 1,
52
+ "x": 360,
53
+ "y": 160,
54
+ "wires": [["fc-webgpu-portal"]]
55
+ },
56
+ {
57
+ "id": "fc-webgpu-portal",
58
+ "type": "portal-react",
59
+ "z": "fc-webgpu-flow",
60
+ "name": "WebGPU Portal",
61
+ "endpoint": "/fromcubes/webgpu",
62
+ "pageTitle": "fromcubes WebGPU",
63
+ "customHead": "",
64
+ "portalAuth": false,
65
+ "showWsStatus": false,
66
+ "libs": [
67
+ { "module": "three", "var": "THREE" }
68
+ ],
69
+ "componentCode": "function App() {\n const { data } = useNodeRed();\n const params = data || { colorA: '#22d3ee', colorB: '#a78bfa' };\n\n return (\n <Page>\n <Header title=\"fromcubes WebGPU\" subtitle=\"Three.js TSL + live Node-RED data\" />\n <TSLCanvas params={params} />\n <TSLStatusBar params={params} />\n </Page>\n );\n}",
70
+ "x": 560,
71
+ "y": 160,
72
+ "wires": [["fc-webgpu-debug"]]
73
+ },
74
+ {
75
+ "id": "fc-webgpu-debug",
76
+ "type": "debug",
77
+ "z": "fc-webgpu-flow",
78
+ "name": "WebGPU output",
79
+ "active": true,
80
+ "tosidebar": true,
81
+ "x": 750,
82
+ "y": 160,
83
+ "wires": []
84
+ }
85
+ ]