This project was bootstrapped with Create React App.
In the project directory, you can run:
Runs the app in the development mode.
Open http://localhost:3000 to view it in the browser.
The page will reload if you make edits.
You will also see any lint errors in the console.
Launches the test runner in the interactive watch mode.
See the section about running tests for more information.
Builds the app for production to the build
folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
See the section about deployment for more information.
Note: this is a one-way operation. Once you eject
, you can’t go back!
- If you aren’t satisfied with the build tool and configuration choices, you can
eject
at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except eject
will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use eject
. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
Builds and serves the app with electron
Builds and packages the app with electron-builder, creates an installer executable in ../output folder
You can learn more in the Create React App documentation.
To learn React, check out the React documentation.
Using Visual Studio Code is recommended for this project because of its built-in support for TypeScript. Make sure the ESLint plugin is installed for enforcing code style and automatic fixes of simple rules (like single-quote strings and tabbing). Most interfaces, types, and methods are documented with a combination of JSDoc and TypeScript.
Pages.tsx
contains a dictionary of unique symbols (think of them like enums). These symbols act as unique identifiers for both pages and projects. For example, anywhere in the app where we wish to refer to the Scope 1 Projects page, we use Pages.scope1Projects
. If you wish to add a new Page symbol, simply add a line to Pages.tsx: <key name>: Symbol('<string description>')
. To convert Page symbols to string, use their .description
property.
Note: In the documentation, the term Page symbol is used frequently. This simply means one of the symbols exported from Pages.tsx
, for example Pages.start
and Pages.winScreen
.
Note 2: The plan was originally to have a new page (e.g. an info dialog box) for each project, but then the ProjectControl
class was created and it was separated into two dictionary objects (PageControls
and Projects
).
PageControls.tsx
contains a dictionary of control objects that tell the main app how to render the main page. The exact structure of a PageControl
object is not necessary to understand for development because you can use generator functions like newStartPageControl
, newInfoDialogControl
, and newGroupedChoicesControl
to create them. This simultaneously helps reduce uneccessary copied code and enables type checking for the different types of controls.
The PageCallback type is defined in functions-and-types.tsx
but it's worth mentioning here. During actions like button presses, the app expects to be told to update its currentPage
, i.e., the symbol associated with the page that should display in the main view. PageCallbacks can either be a Page symbol in and of itself, OR a function which returns a Page symbol. See the JSdoc written in functions-and-types.tsx
for more info.
The top of Projects.tsx
contains Scope1Projects
and Scope2Projects
which must be kept up to date, some type definitions, and a class definition for ProjectControl
. Lower down, there are definitions for each project. ProjectControl
contains properties and methods necessary for integrating projects in the rest of the app. Projects
is a dictionary of ProjectControl
s whose key is a Page symbol.
App.tsx
is the main app and contains the backbone for business logic. It passes properties from its state
into child components, and PageCallbacks + Resolvables are bound to the main app when they execute.
functions-and-types.tsx
contains useful functions and type declarations used across the app. See the JSDoc inside the file for more.
trackedStats.tsx
contains definitions for, and functions related to, the stats that are tracked across the game such as budget and emissions.
- Create a new Page symbol for the project in
Pages.tsx
. - Add it to either
Scope1Projects
orScope2Projects
, depending on which scope it's in. - Create the new project in
Projects.tsx
. See the documentation onProjectControlParams
for info on the necessary parameters.
Projects[Pages.myNewProject] = new ProjectControl({
pageId: Pages.myNewProject,
cost: 50_000,
...
});
- In the newGroupedChoicesControl parameters for
PageControls[Pages.scope1Projects]
, addProjects[Pages.myNewProject].getProjectChoiceControl()
to thechoices
list of one of thegroups
.
- Create a React component inside the
components
subfolder.
import { PureComponentIgnoreFuncs } from './functions-and-types';
import type { ControlCallbacks, PageControl } from './controls';
export class NewComponent extends PureComponentIgnoreFuncs {
constructor(props) {
super(props);
}
render() {
return <h1>Hello world! My name is {this.props.name}</h1>;
}
}
- Define a "control props" interface for the component, for use in PageControls.
interface NewComponentControlProps {
name: string;
}
- Make the component's
props
interface/type extendControlCallbacks
and those control props.
interface NewComponentProps extends ControlCallbacks, NewComponentControlProps { /* Leave it empty */ }
class NewComponent extends PureComponentIgnoreFuncs <NewComponentProps> {
constructor(props: NewComponentProps) {
super(props);
}
render() {
return <h1>Hello world! My name is {this.props.name}</h1>;
}
}
- Make a "newControl" constructor function for use in PageControls, which accepts your "control props" as its first parameter.
export function newNewComponentControl(props: NewComponentControlProps, onBack: PageCallback): PageControl {
return {
componentClass: NewComponent,
controlProps: props,
onBack,
hideDashboard: false, // you could also include hideDasboard as a required property of NewComponentControlProps, like in GroupedChoicesControlProps
}
}
- In
App.tsx
->CurrentPage
, include the new component in the switch statement, depending on what props you wish to pass it.
switch (this.props.componentClass) {
case StartPage:
case GroupedChoices:
case NewComponent:
if (!this.props.controlProps) throw new Error('currentPageProps not defined');
return (<this.props.componentClass
{...this.props.controlProps} // Pass everything into the child
{...controlCallbacks}
/>);
...
}
Most string properties support template text:
- Wrapping text in curly braces
{}
will make them emphasized. For example:"foo {bar} baz"
gets turned into "foo bar baz". Note: Do not confuse these braces with JS template strings:
let x = `hello world, my variable = ${myVariable}!`;
- Wrapping text in curly braces followed by an underscore
_{}
will instead make it subscript . For example:foo_{bar}
gets turned into foobar . - All instances of the newline character
\n
will be replaced by a line break, i.e.<br/>
in HTML. - The following format
[text](url)
will result in a clickable link, similar to Markdown. For example,[Better Plants](https://betterbuildingssolutioncenter.energy.gov/better-plants)
will result in Better Plants.
When an array of strings is passed into parseSpecialText()
, the strings will be joined by two line breaks. This is useful when writing multiple paragraphs.
To add a component that supports this template syntax, you need to use React's dangerouslySetInnerHTML property. Just make sure not to use it for any user-generated input. For example:
return (
// Turn:
<Typography variant='body1'>{myProp}</Typography>
// into:
<Typography variant='body1' dangerouslySetInnerHTML={parseSpecialText(myProp)}/>
)
Some properties in pageControls.tsx
and projects.tsx
still just take strings, instead of supporting Resolvables, i.e., functions that return the desired type. For info on Resolvables, check functions-and-types.tsx
. All you need to do to switch to a Resolvable is change (for example) is:
- In the TypeScript interface declaration for props, change
myProp: string
tomyProp: Resolvable<string>
, and - In code for the component which takes the property, change all instances of
myProp
tothis.props.resolveToValue(myProp)
. All components are passed the functionApp.resolveToValue
, which provides them full access toApp
viathis
as well as the app state via the first called function parameter.
First, search for icons that may work in MUI's Material Icons page. It comes with over 2,000 icons, and they're easy to import.
import BoltIcon from '@mui/icons-material/Bolt';
const icon = <BoltIcon/>;
If no icons that fit what you're looking for, try iconmonstr. They're already included in the attributions section. Download it as an svg, then do the following:
- Create a new file in the
icons
subfolder. For example:SmileSunglassesIcon.tsx
- Add the following to the top:
import React from 'react';
import { createSvgIcon } from '@mui/material';
- Open the svg file downloaded from iconmonstr. Copy everything INSIDE the
<svg></svg>
tags and place it according to the following pattern:
export default createSvgIcon(
<Contents_of_SVG>,
'Name of SVG icon'
);
Example:
<!-- iconmonstr-smiley-20.svg -->
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm0 2c3.691 0 6.915 2.016 8.647 5h-17.294c1.732-2.984 4.956-5 8.647-5zm0 20c-5.514 0-10-4.486-10-10 0-1.045.163-2.052.461-3h1.859c.606 1.518 1.929 3 3.986 3 2.477 0 2.153-2.31 3.694-2.31s1.218 2.31 3.695 2.31c2.055 0 3.379-1.482 3.984-3h1.86c.298.948.461 1.955.461 3 0 5.514-4.486 10-10 10zm5.508-8.059l.492.493c-1.127 1.72-3.199 3.566-5.999 3.566-2.801 0-4.874-1.846-6.001-3.566l.492-.493c1.513 1.195 3.174 1.931 5.509 1.931 2.333 0 3.994-.736 5.507-1.931z"/></svg>
becomes
// icons/SmileSunglassesIcon.tsx
import React from 'react';
import { createSvgIcon } from '@mui/material';
export default createSvgIcon(
<path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm0 2c3.691 0 6.915 2.016 8.647 5h-17.294c1.732-2.984 4.956-5 8.647-5zm0 20c-5.514 0-10-4.486-10-10 0-1.045.163-2.052.461-3h1.859c.606 1.518 1.929 3 3.986 3 2.477 0 2.153-2.31 3.694-2.31s1.218 2.31 3.695 2.31c2.055 0 3.379-1.482 3.984-3h1.86c.298.948.461 1.955.461 3 0 5.514-4.486 10-10 10zm5.508-8.059l.492.493c-1.127 1.72-3.199 3.566-5.999 3.566-2.801 0-4.874-1.846-6.001-3.566l.492-.493c1.513 1.195 3.174 1.931 5.509 1.931 2.333 0 3.994-.736 5.507-1.931z"/>,
'SmileSunglassesIcon',
);
This tool was developed by staff at Oak Ridge National Laboratory (ORNL) and in collaboration with the U.S. Department of Energy. This software tool was funded by the U.S. Department of Energy's Office of Energy Efficiency and Renewable Energy under Oak Ridge National Laboratory Contract No. DE-AC05-00OR22725.
- Gina Accawi
- Kristina Armstrong
- Paulomi Nandy
- Sachin Nimbalkar
- Thomas Wenning
- Rachel Hernandez
- Jordan Lees
Choose Your Own Solution was made possible by open-source software, most notably including:
- React (https://reactjs.org/)
- MUI / Material UI (https://mui.com/)
- visx (https://airbnb.io/visx/)
- react-spring (https://react-spring.dev/)
- Sass (https://sass-lang.com/)
- Node.JS (https://nodejs.org/en/)
- TypeScript (https://www.typescriptlang.org/)
- Icons from iconmonstr (https://iconmonstr.com/)