Comments (30)
In case anyone here is interested,
I needed to use NexusUI with React, so I wrote this library : react-nexusui
Cheers !
from ui.
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.
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.
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.
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.
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.
I tryed to use React too, I'm interested in this!
from ui.
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.
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.
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.
would you share your code, please?
from ui.
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.
My issue now is that I can't do any styling.
My dials look like this by default:
Seems that no styling is shown.
'label' property is ignored also.
Ben? Any idea?
from ui.
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.
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.
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.
from ui.
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.
from ui.
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.
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.
from ui.
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.
oh yes, works fine!
from ui.
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.
@taylorbf Thanx, it looks updated now. I have asked this here #52 as well, sorry for posting here at the wrong issue :)
from ui.
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.
Working on it @taylorbf :)
from ui.
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.
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/orstate
. 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 DialUsage 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 SelectUsage:
<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)
- Piano display bug
- document.write ignored when loading the script asynchronously HOT 4
- Sequencer doesn't play using Vue.js HOT 1
- Pan slider outputs different values on change event HOT 4
- Multislider - numberOfSliders after initialization? HOT 1
- Github pages not showing newest docs HOT 4
- Sequencer's 'change' event triggered even if value does not change HOT 6
- this.parent.style is undefined HOT 4
- "transparent" or "none" support HOT 4
- latest CDN contains git conflict HOT 1
- Multislider resize() workaround
- How to initialize the matrix model in the sequencer? HOT 1
- Provide TypeScript .d.ts type declarations HOT 1
- Number formatting HOT 1
- piano keys didn't release themselves
- Is it possible to change the value without triggering the output event. HOT 1
- How can I use nexusui with nextjs? HOT 1
- Determine mouse y position of clicked key on piano
- What's up with RangeSlider?
- Fixing RadioButton layout
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ui.