Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI #6

Open
TanninOne opened this issue Oct 22, 2020 · 0 comments
Open

UI #6

TanninOne opened this issue Oct 22, 2020 · 0 comments

Comments

@TanninOne
Copy link
Contributor

TanninOne commented Oct 22, 2020

UI

Vortex uses the react library (https://reactjs.org/) for its user interface, you will have to learn a bit about it to do anything useful with Vortex Extensions.
Fortunately it's not too complicated and there is plenty of tutorials and documentation on the Internet, so the following will focus on how it plays together with Vortex.

Example

index.js

const APIView = require('./APIView').default;

function init(context) {
  context.registerMainPage('filter', 'API', APIView, {
    group: 'support',
    props: () => ({ api: context.api }),
  });

  return true;
}

exports.default = init;

Not much is new here. We use registerMainPage to add a whole page, which will show up as a tab on the main navbar.
filter is the name of a predefined icon we use, API the label.
APIView is a custom react component that will be doing the rendering (see below).
group: 'support' specifies where on the navbar (if at all) the button will show up. The navbar is split into groups, from top to bottom they are 'dashboard', 'global', 'per-game' and 'support'.
props specifies parameters that get passed to the react component, in this case we pass in the api so the component can do something with it.
(This isn't actually necessary as you will see later but this example is pretty contrived anyway).

APIView.js

const React = require('react');
const { ListGroup, ListGroupItem, Panel } = require('react-bootstrap');
const { MainPage } = require('vortex-api');

class APIView extends React.Component {
  render() {
    const { api } = this.props;
    return React.createElement(MainPage, null,
      React.createElement(MainPage.Body, null, 
        React.createElement(Panel, null,
          React.createElement(Panel.Body, null,
           React.createElement(ListGroup, null,
            Object.keys(api).map((name, idx) =>
              React.createElement(ListGroupItem, { key: idx }, name)
      ))))));
  }
}

exports.default = APIView;

Now this looks a lot more "interesting".
First of all, we import a couple of modules, react is essentially what we use to create an html page from javascript, react-bootstrap is a helper library to use the famous bootstrap css framework from react and then vortex-api.

At this point you may be wondering

  • "what modules can I import anyway?": Good question. You can import any module that Vortex uses as well without problem. There will be a list in the reference. You can use any other modules as well but then it would be best you used webpack to create a single-file bundle for distribution (See later example).
  • "what is this vortex-api and how is it different from the api object we got in context?": These are really two separate things, I'm just not too creative with names. The context.api object is a stateful object that you use to communicate with vortex (like a handle) whereas vortex-api is more of a library of convenience functionality. There shouldn't be anything in vortex-api you absolutely need to write an extension, it just makes it easier.

React

Ok, past the imports we declare a class which is our react component with a single function render. This function will be called automatically by react as necessary to generate html to be displayed.
What this render function does is create a html subtree that gets inserted into the DOM.
The example generates html like this (slightly simplified):

<div class="main-page">
  <div class="main-page-body">
    <div class="panel">
      <div class="panel-body">
        <ul class="list-group">
          <li class="list-group-item">showErrorNotification</li>
          ...
        </ul>
      </div>
    </div>
  </div>
</div>

So React.createElement(MainPage, ... generates a div with class main-page, nothing too scary.
The real magic happens behind the scenes in that React will automagically update subtrees like these as properties change without touching the parts of the DOM that didn't change.

This is important: React components update automatically when its props change but only if react is aware of the change! If you find a UI component doesn't update, this is almost certainly the reason. I'll go into this in later tutorials.

Functional Programming

So React generates a bunch of divs and a list for us, that just leaves one last bit of code to look at:
Object.keys(api).map((name, idx) => React.createElement(ListGroupItem, { key: idx }, name)

If you have been working with javascript before this is hopefully not too strange:
Object.keys(api) retrieves a list of all the attributes in the api object. Since that object contains only functions, this is a list of the functions the api exposes.
list.map() passes each element of a list through a function and generates a new list from its return values (the resulting list has the same length as the original).
(args) => {} is the modern lambda syntax in javascript, so it defines an anonymous function for map to call.

So what this line does is take all the function names from the api and generate a "ListGroupItem" (aka "li") for each.

Preview: JSX

If you're like me, those nested React.createElement calls look rather nasty. React introduces a sligthly extended js file format that lets you write code like that in a more readable way:

class APIView extends React.Component {
  render() {
    const { api } = this.props;
    return (
      <MainPage>
        <MainPage.Body>
          <Panel>
            <Panel.Body>
              <ListGroup>
                {Object.keys(api).map((name, idx) => <ListGroupItem key={idx}>{name}</ListGroupItem>)}
              </ListGroup>
             </Panel.Body>
           </Panel>
         </MainPage.Body>
       </MainPage>
    );
  }
}

Not shorter but a lot more readable imho. However you can't just import jsx, you need to preprocess it to turn it into usable javascript. See the next tutorial for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant