Git Product home page Git Product logo

Comments (30)

rakannimer avatar rakannimer commented on August 17, 2024 5

In case anyone here is interested,

I needed to use NexusUI with React, so I wrote this library : react-nexusui

Cheers !

from ui.

taylorbf avatar taylorbf commented on August 17, 2024 2

Thanks @hepiyellow for your work sharing this tutorial.

@turbo5 that's a different issue but thanks for catching it -- i've updated the npm registry to 1.2 with #76 .

from ui.

JamesTheHacker avatar JamesTheHacker commented on August 17, 2024 2

I know this is an old issue but it's the first result on Google for "nexus ui react". I am just posting my solution here incase anyone else is wondering how to do this.

You will notice I am generating a random ID. This is because I do not rely on the ID for anything other than generating the dial and is purely personal choice. Everything else will be controlled by props and/or state. This is a rough solution to demonstrate because I felt the solution by @hepiyellow was overly complex.

Here's a Dial Component:

import React from 'react'
import Nexus from 'nexusui'
import randomID from 'random-id'
import PropTypes from 'prop-types'

class Dial extends React.Component {
    static propTypes = {
        size: PropTypes.arrayOf(PropTypes.number),
        interaction: PropTypes.string,
        mode: PropTypes.string,
        min: PropTypes.number,
        max: PropTypes.number,
        step: PropTypes.number,
        value: PropTypes.number,
    }

    static defaultProps = {
        size: [75, 75],
        interaction: 'radial',
        mode: 'relative',
        min: 0,
        max: 1,
        step: 0,
        value: 0,
    }

    state = {
        id: null
    }

    componentWillMount = () => {
        this.setState({ id: randomID(10) })
    }

    onChange = value => {
        console.log(`Dial value changed: ${value}`)
        // You could pass in a callback via props to pass value to parent ...
        //  this.props.onChange(value)
    } 

    componentDidMount = () => {
        const dial = new Nexus.Dial(`#${this.state.id}`, {
            size: this.props.size,
            interaction: this.props.interaction, // "radial", "vertical", or "horizontal"
            mode: this.props.mode, // "absolute" or "relative"
            min: this.props.min,
            max: this.props.max,
            step: this.props.step,
            value: this.props.value
        })
        dial.on('change', this.onChange)
    }

    render() {
        return <div id={this.state.id}></div>
    }
}

export default Dial

Usage like so ...

<Dial size={[ 100, 100 ]}/>

And here is a Select Component

import React from 'react'
import Nexus from 'nexusui'
import randomID from 'random-id'
import PropTypes from 'prop-types'

class Select extends React.Component {
    static propTypes = {
        size: PropTypes.arrayOf(PropTypes.number),
        options: PropTypes.arrayOf(PropTypes.string),
        value: PropTypes.number,
        onChange: PropTypes.func,
    }

    static defaultProps = {
        size: [ 100, 30 ],
        value: null,
        options: [],
        onChange: () => {},
    }

    state = {
        id: null,
        selectedIndex: 0,
        selectedValue: null
    }

    componentWillMount = () => {
        this.setState({ id: randomID(10) })
    }

    onChange = ({ value, index }) => {
        this.setState({
            selectedIndex: index,
            selectedValue: value
        })
        this.props.onChange(value, index)
    } 

    componentDidMount = () => {
        const select = new Nexus.Select(`#${this.state.id}`, {
            size: this.props.size,
            options: this.props.options
        })
        select.on('change', this.onChange)
    }

    render() {
        return <div id={this.state.id}></div>
    }
}

export default Select

Usage:

<Select 
    options={[ 'Major', 'Minor', 'Diminished' ]}
    size={[ 100, 30 ]}
    onChange={(value, index) => console.log(`Value ${value} is at index ${index}`)}
/>

If you guys don't mind I would happily spend some weeks creating a react library for Nexus? I'd love to take on that task if nobody else is doing it as I plan to use Nexus in my many Web Audio projects with React.

from ui.

hepiyellow avatar hepiyellow commented on August 17, 2024 1

I am still investigating this, so can't share code yet. There are more issues ahead.
I am using nx.transform() and nx.widgets[id].destroy() to update nx whenever a relevant DOM change is done.
I am using React's ref callback to do so. It is supposed to be called when the DOM element is mounted and unmounted.
But React has a reconciliation process where it can decide to only update a DOM element's attributes, instead of unmounting and mounting a new Element. And it seems that the ref callback is called for an unmount (with arg null) also when only an DOM update is done.

I will share some code or explain the whole solution once I have it working. (might take a few days)

from ui.

hepiyellow avatar hepiyellow commented on August 17, 2024 1

Ok, main issue solved. And turns out there no need for any change in NexusUI. Even no need to change 'nx' to 'data-nx'.

Here is my minimal React component wrapper for a NexusUI canvas (using typescript):

type NxType = string; //'dial' | 'slider' ,etc...

export interface INexusUICanvasProps {
    type: NxType
    attrs?: {
        [index: string]: any
    }
}

export default class NexusUICanvas extends Component<INexusUICanvasProps, {}> {
    mountedCanvas: any;

    componentDidMount() {
        this.mountedCanvas.setAttribute('nx', this.props.type);
        if (this.props.attrs) {
            Object.keys(this.props.attrs).forEach((attrName) => {
                this.mountedCanvas.setAttribute(attrName, this.props.attrs[attrName]);
            });
        }
        (window as any).nx.transform(this.mountedCanvas);
    }

    componentWillUnmount() {
        (window as any).nx.widgets[this.mountedCanvas.id].destroy();
    }

    render() {
        return (
            <canvas ref={this.refCallback.bind(this)} />
        )
    }

    refCallback(domElement: any) {
        this.mountedCanvas = domElement;
    }
}

The solution to the issue I mentioned before is not to do anything in the ref callback except update the components this.mountedCanvas property. And do everything in componentDidMount and componentWillUnmount.

So actually the nx property and any other property you want to pass to the canvas is assigned in the componentDidMount before calling nx.transform.

It seems that the whole lifecycle of Nexus canvas is working.

from ui.

hepiyellow avatar hepiyellow commented on August 17, 2024 1

There was another issue with resizing.
When the NexuxUICanvas component gets updated and the style.height / style.width changes, it didn't update the widget. So I added a call to widget,resize(). While at it also added a call to widget.checkPercentage() in case the style was changed to be percentage based.

It would be best if the widget had an reInitFromCanvas() method which re-initializes all internal state which is derived from the canvas.

Here is my current version of NexusUICanvas (with typescript):

import * as React from 'react';
import { Component } from 'react';

import * as Debug from "debug";
var debug = Debug('LM2.NexusUICanvas');

export type NxType = string; //'dial' | 'matrix' ,etc...

export interface NxWidget {
    canvas: any;
    height: number;
    width: number;
    preMove: any;
    preRelease: any;
    canvasID: any;
    init(): void;
    draw(): void;
    resize(w?: number, h?: number): void;
    checkPercentage(): void;
    transmit(data: any): void;
    set(value: any): void;
    getName(): string;
    [index: string]: any;
}

interface Attributes {
    [index: string]: any
}

export interface INexusUICanvasPropsBase {
    /**
     * A style object to be set on the created canvas element.
     */
    style?: {
        height: number,
        width: number,
        [index: string]: any
    }

    /**
     * An object containing properties that are to be set on the created 
     * canvas element before it is transformed into an NX interface.
     */
    canvasAttrs?: Attributes
    /**
     * An object containing properties that are to be set on the created
     * widget.
     */
    widgetAttrs?: Attributes
}

export interface INexusUICanvasProps extends INexusUICanvasPropsBase {
    type: NxType

}

// Need to make sure nx.globalWidgets = false to avoid global variable to the window object,
// which is 
(window as any).nx.globalWidgets = false;



export default class NexusUICanvas extends Component<INexusUICanvasProps, {}> {
    mountedCanvas: any;
    widget: NxWidget;

    componentDidMount() {
        debug('componentDidMount() canvas.id: ', this.mountedCanvas.id, 'elm=', this.mountedCanvas);
        // We set the canvas attribute 'nx' = type instead of calling transform(type),
        // because manager.blockMove() is looking for the canvas.nx attribute.
        this.mountedCanvas.setAttribute('nx', this.props.type);
        this.updateCanvasFromProps();
        
        // widget has internal state which is initialized (by the canvas) only when the widget is created.
        // If the canvas is changed, we need to create a new widget.
        // For example widget.height is initialized from canvas style when creating a new Widget. But it 
        // doesn't update when canvas style is changed.
        this.widget = (window as any).nx.transform(this.mountedCanvas);

        this.updateWidgetFromProps();
        this.widget.draw();
    }
   
    componentWillUnmount() {
        debug('componentWillUnmount() destroy canvas.id= ', this.mountedCanvas.id);

        // We don't call the original widget.destroy() because it 
        // removes the canvas from the DOM, and we should leave that to React.
        NexusUICanvas.destroyWidget(this.widget);
    }

    componentWillUpdate() {
        debug('componentWillUpdate()');
    }

    shouldComponentUpdate(nextProps: INexusUICanvasProps, nextState: any) {
        //TODO: optimize. 
        return true;
    }

    componentDidUpdate() {
        debug('componentDidUpdate() widget=', this.widget);
        this.updateCanvasFromProps();
        this.updateWidgetFromProps();
    }

    render() {
        debug('render(): props=', this.props);
        return (
            <canvas
                style={this.props.style}
                ref={this.refCallback.bind(this)}>
            </canvas>
        )
    }

    refCallback(domElement: any) {
        debug('refCallback() domElm=', domElement);
        this.mountedCanvas = domElement;
    }

    updateCanvasFromProps() {
        const attrs = this.props.canvasAttrs;
        if (attrs) {
            Object.keys(attrs).forEach((attrName) => {
                this.mountedCanvas.setAttribute(attrName, attrs[attrName]);
            });
        }
    }

    updateWidgetFromProps() {
        const attrs = this.props.widgetAttrs;
        if (attrs) {
            Object.keys(attrs).forEach((attrName) => {
                this.widget[attrName] = attrs[attrName];
            });
        }

        // This is partial solution if we don't re-create a new widget when component is updated
        if (this.props.style) {
            this.widget.resize(this.props.style.width, this.props.style.height)
            this.widget.checkPercentage();
        }

        this.widget.init();
    }

    /**  @method destroy
    Remove the widget object, canvas, and all related event listeners from the document.
    */
    static destroyWidget(widget: NxWidget) {
        const nx = (window as any).nx;
        var type = nx.elemTypeArr.indexOf(widget.getName())
        nx.elemTypeArr.splice(type, 1)

        widget.canvas.ontouchmove = null;
        widget.canvas.ontouchend = null;
        widget.canvas.onclick = null;
        widget.canvas.onmousemove = null;
        widget.canvas.onmouseoff = null;
        document.removeEventListener("mousemove", widget.preMove, false);
        document.removeEventListener("mouseup", widget.preRelease, false);

        // Commented-out original code which is inappropriate for React (EM)
        // var elemToKill = document.getElementById(this.canvasID)
        // if (elemToKill) {
        //   elemToKill.parentNode.removeChild(elemToKill);
        // }

        var id = widget.canvasID
        delete nx.widgets[id];
        // Added a check for nx.globalWidgets (EM)
        if ((window as any).nx.globalWidgets === true) {
            delete window[id];
        }
    }
}

from ui.

brucelane avatar brucelane commented on August 17, 2024

I tryed to use React too, I'm interested in this!

from ui.

taylorbf avatar taylorbf commented on August 17, 2024

Hey there @hepiyellow , I'll do my best to answer this, albeit with very little knowledge of react.

It sounds like you might be able to use nx.transform() instead of nx.add()

nx.add() creates a canvas element and turns it into a nexusUI interface

nx.transform() transforms an existing canvas element into a nexusUI interface

Let me know if this is a viable solution and I'll do my best to figure out alternative solutions if it's not!

from ui.

hepiyellow avatar hepiyellow commented on August 17, 2024

Well,
I didn't find a solution for the scenario I proposed. But I did solve the problem using nx.transform().
In my scenario a Main React component is using the conditional rendering shown above. So any children of my Main component will not be transformed be NexusUI.

The solution is to call nx.transform() when a canvas is mounted.
Need to add the special React ref property to the canvas as such :
<canvas data-nx='whatever' ref={(canvas)=>{window.nx.transform(canvas)} /> The callback passed to ref will be called when the element is mounted, and canvas` will be the DOM element.

I am just not sure yet if and how I "un-transform" a element which has been unmounted. (unmounted=removed from the DOM without a document re-load)
Is there a NexusUI function for that?

from ui.

taylorbf avatar taylorbf commented on August 17, 2024

I think the best way to do this, currently, is to use widget.destroy()

So, if your slider looks like this....

<canvas data-nx="slider" id="masterfader"></canvas>

Then you could call this when you want to remove it:

nx.widgets.masterfader.destroy()

from ui.

brucelane avatar brucelane commented on August 17, 2024

would you share your code, please?

from ui.

brucelane avatar brucelane commented on August 17, 2024

thank you, I'm not in a hurry so take your time.
I made a website with React/nexusui last month you can see on http://videodromm.com/
It sends websocket messages to a nodejs websocket server.
It's working though it's not clean...

from ui.

hepiyellow avatar hepiyellow commented on August 17, 2024

My issue now is that I can't do any styling.
My dials look like this by default:
screen shot 2017-01-18 at 2 27 14 pm

Instead of like this:
screen shot 2017-01-18 at 2 35 59 pm

Seems that no styling is shown.
'label' property is ignored also.

Ben? Any idea?

from ui.

taylorbf avatar taylorbf commented on August 17, 2024

I'm not sure. I have a few guesses.

In preliminary tests (in vanilla js), I did not experience this issue. The following code produced a styled dial with a label:

function addComponent() {
          var c = document.createElement("canvas")
          c.setAttribute("nx","dial")
          c.setAttribute("label","volume")
          document.body.appendChild(c)
          nx.transform(c)
}

The dial you are seeing is also an older style. I wonder if you are using an older version of NexusUI?

Your react strategy -- using nx.transform, widget.destroy, etc -- will work in the newest version as well, I believe.

Lastly, just a note that there is an optional second argument for nx.transform that denotes the widget's type. That might be more elegant than manually giving your canvas an nx attribute.

nx.transform( canvasElement, "dial" )

Good luck and also thank you for your work getting nexusUI working with React! I am terribly sorry that I was not more help earlier in the process. I graduated from the university where this was created as a research project, and I'm not sure they have found anyone to replace me on authorship yet. We will add a note to the website indicating as much. I hope to do a refactor later this year, in which case I would love to discuss ways to increase compatibility with React.

from ui.

hepiyellow avatar hepiyellow commented on August 17, 2024

Yep. I was using an old version of NexusUI. Thanks (-:

About using nx.transform( canvasElement, "dial" ) with the type as the second argument. I saw blockMove() method which seems to be checking for a the nx attribute on a DOM element (I guess a canvas), and I didn't see that calling transform adds the nx attribute to the given canvas.
So, either this is a bug, of the blockMove() method is not relevant. I just wanted to be on the safe side.

from ui.

hepiyellow avatar hepiyellow commented on August 17, 2024

One last issue, I'll check tomorrow,
is that is see all my nexusUI interfaces animate (less than 0.5 sec) from position 0,0 to their final position (along with the whole other DOM elements) every time they are created (and transformed).

Any pointers on that?

BTW: How do I change the label's font color?

from ui.

taylorbf avatar taylorbf commented on August 17, 2024

from ui.

hepiyellow avatar hepiyellow commented on August 17, 2024

Putting the init issue aside.
I found a new issue with using widget.destroy(). It seems that it also removes the canvas from the DOM:

var elemToKill = document.getElementById(this.canvasID)
if (elemToKill) {
    elemToKill.parentNode.removeChild(elemToKill);
}

This might lead to a React error:

 Uncaught TypeError: Cannot read property 'replaceChild' of null
    at Function.replaceChildWithTree (DOMLazyTree.js:70)

I encounter this case, and removing those line solves it.
I think nexusUI (when working with React) should not intervene with DOM structure.

I also see in the widget.destroy() function, these lines:

var id = this.canvasID
  delete nx.widgets[id];
  delete window[id];

Are you adding the canvas IDs as properties on the global window object? it that safe?

from ui.

taylorbf avatar taylorbf commented on August 17, 2024

from ui.

taylorbf avatar taylorbf commented on August 17, 2024

Hi @hepiyellow,

Yes, widget.resize( w, h ) is the best way to resize an interface element.

There is an .init() method for each widget, which is called at the end of .resize(). .init() is mostly for the purpose of re-calculating size-specific variables. Can you explain further what you mean by re-initializing internal states which are derived from the canvas?

By the way, I noticed a bug with .resize and the latest dial... if given a non-square canvas, the dial does not center correctly.

I am working on a refactor a.t.m., taking into account some of your concerns with react as well as other concerns from the past year. I imagine this revision will take some time, but I wanted to let you know it is in the works.

Ben

from ui.

brucelane avatar brucelane commented on August 17, 2024

hi @hepiyellow , could you make a repo with a sample project?
I don't know about typescript, I guess it needs a transpiler task with gulp or webpack?
thank you very much
Bruce

from ui.

hepiyellow avatar hepiyellow commented on August 17, 2024

from ui.

hepiyellow avatar hepiyellow commented on August 17, 2024

Here is my example for using NexusUI with React (and typescript)
It is still under construction. Yet some things to solve...

I think NexusUI is great to use with Mobx, I will add examples later on how to do it.

https://github.com/hepiyellow/nexusui-mobx-react-typescript-sample

from ui.

brucelane avatar brucelane commented on August 17, 2024

oh yes, works fine!

from ui.

turbo5 avatar turbo5 commented on August 17, 2024

In the npm registry (npm install) the version is still 1.0.8, sith the old widgets, and no labels, no preset values. Can you update it please?

from ui.

turbo5 avatar turbo5 commented on August 17, 2024

@taylorbf Thanx, it looks updated now. I have asked this here #52 as well, sorry for posting here at the wrong issue :)

from ui.

taylorbf avatar taylorbf commented on August 17, 2024

Thank you so much! This is great. Yes a react/Nexus library would be welcome.

Accomplishing this with version 2 should be a lot more straightforward than it was in version 1. Please post issues if you find them.

from ui.

JamesTheHacker avatar JamesTheHacker commented on August 17, 2024

Working on it @taylorbf :)

from ui.

 avatar commented on August 17, 2024

I am trying to figure out how to use the Nexus.Piano, would it be similar to how this dial is set-up?

from ui.

cyrusvahidi avatar cyrusvahidi commented on August 17, 2024

I know this is an old issue but it's the first result on Google for "nexus ui react". I am just posting my solution here incase anyone else is wondering how to do this.

You will notice I am generating a random ID. This is because I do not rely on the ID for anything other than generating the dial and is purely personal choice. Everything else will be controlled by props and/or state. This is a rough solution to demonstrate because I felt the solution by @hepiyellow was overly complex.

Here's a Dial Component:

import React from 'react'
import Nexus from 'nexusui'
import randomID from 'random-id'
import PropTypes from 'prop-types'

class Dial extends React.Component {
    static propTypes = {
        size: PropTypes.arrayOf(PropTypes.number),
        interaction: PropTypes.string,
        mode: PropTypes.string,
        min: PropTypes.number,
        max: PropTypes.number,
        step: PropTypes.number,
        value: PropTypes.number,
    }

    static defaultProps = {
        size: [75, 75],
        interaction: 'radial',
        mode: 'relative',
        min: 0,
        max: 1,
        step: 0,
        value: 0,
    }

    state = {
        id: null
    }

    componentWillMount = () => {
        this.setState({ id: randomID(10) })
    }

    onChange = value => {
        console.log(`Dial value changed: ${value}`)
        // You could pass in a callback via props to pass value to parent ...
        //  this.props.onChange(value)
    } 

    componentDidMount = () => {
        const dial = new Nexus.Dial(`#${this.state.id}`, {
            size: this.props.size,
            interaction: this.props.interaction, // "radial", "vertical", or "horizontal"
            mode: this.props.mode, // "absolute" or "relative"
            min: this.props.min,
            max: this.props.max,
            step: this.props.step,
            value: this.props.value
        })
        dial.on('change', this.onChange)
    }

    render() {
        return <div id={this.state.id}></div>
    }
}

export default Dial

Usage like so ...

<Dial size={[ 100, 100 ]}/>

And here is a Select Component

import React from 'react'
import Nexus from 'nexusui'
import randomID from 'random-id'
import PropTypes from 'prop-types'

class Select extends React.Component {
    static propTypes = {
        size: PropTypes.arrayOf(PropTypes.number),
        options: PropTypes.arrayOf(PropTypes.string),
        value: PropTypes.number,
        onChange: PropTypes.func,
    }

    static defaultProps = {
        size: [ 100, 30 ],
        value: null,
        options: [],
        onChange: () => {},
    }

    state = {
        id: null,
        selectedIndex: 0,
        selectedValue: null
    }

    componentWillMount = () => {
        this.setState({ id: randomID(10) })
    }

    onChange = ({ value, index }) => {
        this.setState({
            selectedIndex: index,
            selectedValue: value
        })
        this.props.onChange(value, index)
    } 

    componentDidMount = () => {
        const select = new Nexus.Select(`#${this.state.id}`, {
            size: this.props.size,
            options: this.props.options
        })
        select.on('change', this.onChange)
    }

    render() {
        return <div id={this.state.id}></div>
    }
}

export default Select

Usage:

<Select 
    options={[ 'Major', 'Minor', 'Diminished' ]}
    size={[ 100, 30 ]}
    onChange={(value, index) => console.log(`Value ${value} is at index ${index}`)}
/>

If you guys don't mind I would happily spend some weeks creating a react library for Nexus? I'd love to take on that task if nobody else is doing it as I plan to use Nexus in my many Web Audio projects with React.

I've had a problem using this approach. When passing a callback to a Nexus components on method, the called method will refer to any this references in the Nexus context and not in the context of the Component.

E.g when onChange is called by Nexus it will called on changes to the dial this.props.onChange(value, index) fail because this is the Nexus class.

from ui.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.