@aaqu/fromcubes-portal-react 0.1.0-alpha.22 → 0.1.0-alpha.24

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.
@@ -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
- ]
@@ -1,72 +0,0 @@
1
- [
2
- {
3
- "id": "fc-util-flow",
4
- "type": "tab",
5
- "label": "Utility: useDebounce",
6
- "disabled": false
7
- },
8
- {
9
- "id": "fc-util-tick",
10
- "type": "inject",
11
- "z": "fc-util-flow",
12
- "name": "Fast tick (10Hz)",
13
- "props": [{ "p": "payload" }],
14
- "repeat": "0.1",
15
- "payload": "",
16
- "payloadType": "date",
17
- "x": 160,
18
- "y": 160,
19
- "wires": [["fc-util-noisy"]]
20
- },
21
- {
22
- "id": "fc-util-noisy",
23
- "type": "function",
24
- "z": "fc-util-flow",
25
- "name": "Noisy signal",
26
- "func": "// Random walk between 0..120 with bursts of noise — exactly the sort of\n// fast-changing input where a debounced display is much more readable\n// than the raw value.\nconst prev = context.get('v') ?? 50;\nconst drift = (Math.random() - 0.5) * 8;\nconst noise = Math.random() < 0.15 ? (Math.random() - 0.5) * 30 : 0;\nconst v = Math.max(0, Math.min(120, prev + drift + noise));\ncontext.set('v', v);\nmsg.payload = { value: +v.toFixed(2), ts: Date.now() };\nreturn msg;",
27
- "outputs": 1,
28
- "x": 360,
29
- "y": 160,
30
- "wires": [["fc-util-portal"]]
31
- },
32
- {
33
- "id": "fc-util-helpers",
34
- "type": "fc-portal-utility",
35
- "z": "fc-util-flow",
36
- "name": "math helpers + useDebounce",
37
- "utilName": "mathHelpers",
38
- "utilCode": "// Constants — bundled once, shared across portals that reference them.\nconst CLAMP_MIN = 0;\nconst CLAMP_MAX = 100;\n\n// Pure helper.\nfunction clamp(n, min, max) {\n return Math.max(min, Math.min(max, n));\n}\n\n// Custom React hook — name MUST start with `use` for React's rules of hooks.\n// This node groups a constant + a helper + a hook in one block; portals that\n// reference any of `clamp`, `useDebounce`, `CLAMP_MIN`, `CLAMP_MAX` pull in\n// the whole node.\nfunction useDebounce(value, ms = 300) {\n const [v, setV] = React.useState(value);\n React.useEffect(() => {\n const t = setTimeout(() => setV(value), ms);\n return () => clearTimeout(t);\n }, [value, ms]);\n return v;\n}",
39
- "x": 160,
40
- "y": 280,
41
- "wires": []
42
- },
43
- {
44
- "id": "fc-util-bar",
45
- "type": "fc-portal-component",
46
- "z": "fc-util-flow",
47
- "name": "Bar",
48
- "compName": "Bar",
49
- "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}",
50
- "compInputs": "value,label",
51
- "compOutputs": "",
52
- "x": 360,
53
- "y": 280,
54
- "wires": []
55
- },
56
- {
57
- "id": "fc-util-portal",
58
- "type": "portal-react",
59
- "z": "fc-util-flow",
60
- "name": "Debounce demo",
61
- "subPath": "debounce",
62
- "pageTitle": "useDebounce demo",
63
- "customHead": "",
64
- "componentCode": "function App() {\n const { data } = useNodeRed();\n const raw = data?.value ?? 0;\n\n // Live raw signal vs debounced views at three different windows.\n // `useDebounce` is declared in the fc-portal-utility node — no import.\n const slow100 = useDebounce(raw, 100);\n const slow400 = useDebounce(raw, 400);\n const slow1500 = useDebounce(raw, 1500);\n\n return (\n <div className=\"min-h-screen bg-zinc-950 text-zinc-100 p-8 font-sans\">\n <h1 className=\"text-2xl font-light text-amber-400 mb-2\">useDebounce</h1>\n <p className=\"text-sm text-zinc-500 mb-8\">\n Custom hook from <code className=\"text-amber-300\">fc-portal-utility</code>. Same noisy 10 Hz input fed through different debounce windows.\n </p>\n <div className=\"max-w-xl space-y-4\">\n <Bar value={raw} label=\"raw\" />\n <Bar value={slow100} label=\"100 ms\" />\n <Bar value={slow400} label=\"400 ms\" />\n <Bar value={slow1500} label=\"1500 ms\" />\n </div>\n </div>\n );\n}",
65
- "libs": [],
66
- "portalAuth": false,
67
- "showWsStatus": false,
68
- "x": 580,
69
- "y": 160,
70
- "wires": [[]]
71
- }
72
- ]