@aaqu/fromcubes-portal-react 0.1.0-alpha.21 → 0.1.0-alpha.23
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 +91 -9
- package/examples/D3 Poland Map.json +90 -0
- package/examples/Live Chart.json +103 -0
- package/examples/PixiJS Sprites.json +118 -0
- package/examples/Sensor Portal.json +97 -0
- package/examples/Shared Components.json +78 -0
- package/examples/Three.js Scene.json +122 -0
- package/examples/Utility Hooks.json +96 -0
- package/examples/WebGPU Shader.json +114 -0
- package/nodes/lib/assets.js +141 -11
- package/nodes/lib/helpers.js +308 -12
- package/nodes/lib/hooks.js +38 -0
- package/nodes/lib/page-builder.js +145 -32
- package/nodes/lib/router.js +25 -1
- package/nodes/portal-react.html +826 -218
- package/nodes/portal-react.js +1193 -180
- package/package.json +20 -5
- package/examples/001-shared-components-flow.json +0 -68
- package/examples/002-sensor-portal-flow.json +0 -71
- package/examples/003-chart-portal-flow.json +0 -93
- package/examples/004-d3-poland-flow.json +0 -80
- package/examples/005-threejs-portal-flow.json +0 -87
- package/examples/006-pixi-portal-flow.json +0 -86
- package/examples/007-webgpu-tsl-flow.json +0 -85
|
@@ -1,86 +0,0 @@
|
|
|
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
|
-
"subPath": "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
|
-
]
|
|
@@ -1,85 +0,0 @@
|
|
|
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
|
-
"subPath": "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
|
-
]
|