Creating a React based Webinterface for [jweb] similar to n4m.monitor
maybites
9月 29 2024 | 4:36 午後
Hi
I am exploring the possibilities for the object [jweb]. specifically the generation of React-js based static webinterfaces as used for the node 4 max monitor window.
I am able to compile a react webapp that loads statically from a local file in [jweb], but i am struggling now to integrate hooks to max inlets and outlets.
I know this must be possible, since it has been realized with the n4m.monitor. The n4m.monitor.min.js file is not giving away lots of its secreets, but I was able to find the function calls for max.outlet and max.bindInlet:
they look something like:
{key:"revealFile",value:function(A){this._max.outlet("reveal",A)}}
or
A._max.bindInlet("npm",A._onNPM.bind(mw(A)))
I assume [jweb] provides these functionalies to interact with the max environment by means of code injection? similar to the 'executejavascript' message?
but I would like to know how it could be implemented in the development of a react app.
maybites
10月 01 2024 | 8:15 午後
I found a way to use https://github.com/flyover/imgui-js and implement the Javascript API for jweb into the typescript source file of the example that comes with the repo.
For reference (and yeah - an AI has done the job - after some coaxing):
1. First, let's declare the `max` property on the `Window` interface:
declare global {
interface Window {
max?: {
bindInlet: (message: string, callback: (...args: any[]) => void) => void;
outlet: (message: string, ...args: any[]) => void;
};
}
}
Note the `?` after `max`, which makes it an optional property. This is because `max` might not always be present (e.g., when not running in the Max/MSP environment).
2. Then, we need to ensure that the `window.max` object is available. We can do this by checking for its existence and waiting if necessary:
function waitForMax(): Promise<void> {
return new Promise((resolve) => {
if (window.max) {
resolve();
} else {
const checkInterval = setInterval(() => {
if (window.max) {
clearInterval(checkInterval);
resolve();
}
}, 100);
}
});
}
3. Modify the `main` function to wait for the Max environment:
export default async function main(): Promise<void> {
await ImGui.default();
await waitForMax();
if (typeof(window) !== "undefined") {
window.requestAnimationFrame(_init);
} else {
// ... existing code for non-browser environments ...
}
}
4. In the `_init` function, set up the Max message bindings:
async function _init(): Promise<void> {
// ... existing ImGui setup code ...
if (window.max) {
window.max.bindInlet("setValue", (v: number) => {
f = Math.min(Math.max(v, 0), 1); // Assuming f is in 0-1 range
});
window.max.bindInlet("toggleDemo", () => {
show_demo_window = !show_demo_window;
});
}
// ... rest of existing code ...
}
5. Create a helper function to send messages to Max:
function sendToMax(message: string, ...args: any[]): void {
if (window.max) {
window.max.outlet(message, ...args);
}
}
6. Use this helper function in your ImGui controls:
function _loop(time: number): void {
// ... existing ImGui setup code ...
ImGui.Begin("Hello, world!");
if (ImGui.SliderFloat("float", (value = f) => f = value, 0.0, 1.0)) {
if (window.max) {
sendToMax("value", f * 127); // Scale to 0-127 range for Max
}
}
if (ImGui.Button("Toggle Demo")) {
show_demo_window = !show_demo_window;
if (window.max) {
sendToMax("demoToggled", show_demo_window ? 1 : 0);
}
}
// ... rest of existing code ...
}
make build-example
and patch: