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

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 { 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}",
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
+ ]