Implement initial, static version of input GUI

This commit is contained in:
2022-03-14 11:10:51 -05:00
parent 92d30460b9
commit 0e53a4334e
30 changed files with 28785 additions and 0 deletions

22
relog-web/src/Button.js Normal file
View File

@@ -0,0 +1,22 @@
import styles from './Button.module.css'
const Button = (props) => {
let className = styles.Button
if (props.kind === "inline") {
className += " " + styles.inline
}
let tooltip = "";
if (props.tooltip != undefined) {
tooltip = <span className={styles.tooltip}>{props.tooltip}</span>
}
return (
<button className={className}>
{tooltip}
{props.label}
</button>
)
}
export default Button;

View File

@@ -0,0 +1,65 @@
.Button {
padding: 6px 36px;
margin: 12px 6px;
line-height: 24px;
border: var(--box-border);
/* background-color: white; */
box-shadow: var(--box-shadow);
border-radius: var(--border-radius);
cursor: pointer;
color: rgba(0, 0, 0, 0.8);
text-transform: uppercase;
font-weight: bold;
font-size: 12px;
background: linear-gradient(
rgb(255, 255, 255) 25%,
rgb(245, 245, 245) 100%
)
}
.Button:hover {
background: rgb(245, 245, 245);
}
.Button:active {
background: rgba(220, 220, 220);
}
.inline {
padding: 0 12px;
margin: 2px 4px 2px 0;
height: 32px;
font-size: 11px;
}
/* .inline:last-child {
margin: 2px 1px;
} */
.tooltip {
visibility: hidden;
background-color: #333;
color: white;
opacity: 0%;
width: 180px;
margin-top: 36px;
margin-left: -180px;
position: absolute;
z-index: 100;
text-transform: none;
font-size: 13px;
border-radius: 4px;
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.25);
line-height: 18px;
padding: 6px;
transition: opacity .5s;
font-weight: normal;
text-align: left;
padding: 6px 12px;
}
.Button:hover .tooltip {
visibility: visible;
opacity: 100%;
transition: opacity .5s;
}

View File

@@ -0,0 +1,7 @@
import styles from './ButtonRow.module.css'
const ButtonRow = (props) => {
return <div className={styles.ButtonRow}>{props.children}</div>
}
export default ButtonRow;

View File

@@ -0,0 +1,3 @@
.ButtonRow {
text-align: center;
}

7
relog-web/src/Card.js Normal file
View File

@@ -0,0 +1,7 @@
import styles from './Card.module.css'
const Card = (props) => {
return (<div className={styles.Card}>{props.children}</div>)
}
export default Card;

View File

@@ -0,0 +1,22 @@
.Card {
border: var(--box-border);
box-shadow: var(--box-shadow);
border-radius: var(--border-radius);
background-color: white;
padding: 12px;
min-height: 24px;
}
.Card h1 {
margin: 12px -12px 0px -12px;
padding: 6px 12px 0px 12px;
font-size: 14px;
line-height: 35px;
border-top: 1px solid #ddd;
}
.Card h1:first-child {
margin: -12px -12px 0px -12px;
border-top: none;
background: none;
}

View File

@@ -0,0 +1,55 @@
import form_styles from './Form.module.css'
import Button from './Button'
const DictInputRow = (props) => {
let unit = "";
if (props.unit) {
unit = <span className={form_styles.FormRow_unit}>({props.unit})</span>
}
let tooltip = "";
if (props.tooltip != undefined) {
tooltip = <Button label="?" kind="inline" tooltip={props.tooltip} />
}
let value = {}
if (props.value != undefined) {
value = props.value;
}
if (props.disableKeys === undefined) {
value[""] = "";
}
const form = []
Object.keys(value).forEach((key, index) => {
let label = <span>{props.label} {unit}</span>;
if (index > 0) {
label = "";
}
form.push(
<div className={form_styles.FormRow} key={index}>
<label>{label}</label>
<input
type="text"
data-index={index}
value={key}
placeholder={props.keyPlaceholder}
disabled={props.disableKeys}
/>
<input
type="text"
data-index={index}
value={value[key]}
placeholder={props.valuePlaceholder}
/>
{tooltip}
</div>
);
});
return <>
{form}
</>;
}
export default DictInputRow;

View File

@@ -0,0 +1,21 @@
import form_styles from './Form.module.css'
import Button from './Button'
const FileInputRow = (props) => {
let tooltip = "";
if (props.tooltip != undefined) {
tooltip = <Button label="?" kind="inline" tooltip={props.tooltip} />
}
return <div className={form_styles.FormRow}>
<label>{props.label}</label>
<input type="text" disabled="disabled" />
<Button label="Upload" kind="inline" />
<Button label="Clear" kind="inline" />
<Button label="Template" kind="inline" />
{tooltip}
</div>;
}
export default FileInputRow;

10
relog-web/src/Footer.js Normal file
View File

@@ -0,0 +1,10 @@
import styles from './Footer.module.css'
const Footer = () => {
return <div className={styles.Footer}>
<p>RELOG: Reverse Logistics Optimization</p>
<p>Copyright &copy; 2020&mdash;2022, UChicago Argonne, LLC. All Rights Reserved.</p>
</div>
}
export default Footer;

View File

@@ -0,0 +1,11 @@
.Footer {
background-color: rgba(0, 0, 0, 0.8);
padding: 24px;
margin-top: 24px;
color: rgba(255, 255, 255, 0.5);
text-align: center;
font-size: 14px;
line-height: 8px;
min-width: 900px;
}

5
relog-web/src/Form.js Normal file
View File

@@ -0,0 +1,5 @@
const Form = (props) => {
return <>{props.children}</>;
}
export default Form;

View File

@@ -0,0 +1,23 @@
.FormRow {
display: flex;
line-height: 24px;
}
.FormRow label {
width: 350px;
padding: 6px 12px;
text-align: right;
}
.FormRow input {
flex: 1;
font-family: monospace;
border: var(--box-border);
border-radius: var(--border-radius);
padding: 4px;
margin: 2px 3px;
}
.FormRow_unit {
color: rgba(0, 0, 0, 0.4);
}

13
relog-web/src/Header.js Normal file
View File

@@ -0,0 +1,13 @@
import styles from './Header.module.css'
const Header = () => {
return (
<div className={styles.HeaderBox}>
<div className={styles.HeaderContent}>
<h1>RELOG</h1>
</div>
</div>
)
}
export default Header;

View File

@@ -0,0 +1,19 @@
.HeaderBox {
background-color: white;
border-bottom: var(--box-border);
box-shadow: var(--box-shadow);
padding: 0;
margin: 0;
}
.HeaderContent {
margin: 0 auto;
max-width: var(--site-width);
}
.HeaderContent h1 {
line-height: 48px;
font-size: 28px;
padding: 12px;
margin: 0;
}

View File

@@ -0,0 +1,26 @@
import React from 'react';
import './index.css';
import PipelineBlock from './PipelineBlock';
import ParametersBlock from './ParametersBlock';
import ProductBlock from './ProductBlock';
import PlantBlock from './PlantBlock';
import ButtonRow from './ButtonRow';
import Button from './Button';
const InputPage = () => {
return <>
<PipelineBlock />
<ParametersBlock />
<ProductBlock name="Battery" />
<ProductBlock name="Nickel" />
<ProductBlock name="Metal casing" />
<PlantBlock name="Battery Recycling Plant" />
<ButtonRow>
<Button label="Load" />
<Button label="Save" />
</ButtonRow>
</>
}
export default InputPage;

View File

@@ -0,0 +1,36 @@
import Section from './Section'
import Card from './Card'
import Form from './Form'
import TextInputRow from './TextInputRow'
const ParametersBlock = () => {
return (
<>
<Section title="Parameters" />
<Card>
<Form>
<TextInputRow
label="Time horizon"
unit="years"
tooltip="Number of years in the simulation."
default="1"
/>
<TextInputRow
label="Building period"
unit="years"
tooltip="List of years in which we are allowed to open new plants. For example, if this parameter is set to [1,2,3], we can only open plants during the first three years. By default, this equals [1]; that is, plants can only be opened during the first year."
default="[1]"
/>
<TextInputRow
label="Annual inflation rate"
unit="%"
tooltip="Rate of inflation applied to all costs."
default="0"
/>
</Form>
</Card>
</>
)
}
export default ParametersBlock;

View File

@@ -0,0 +1,87 @@
import React from 'react';
import ReactFlow, { Background } from 'react-flow-renderer';
import Section from './Section';
import Card from './Card';
import Button from './Button';
import styles from './PipelineBlock.module.css';
const elements = [
{
id: '1',
data: { label: 'Battery' },
sourcePosition: 'right',
targetPosition: 'left',
position: { x: 100, y: 200 },
className: styles.ProductNode,
},
{
id: '2',
data: { label: "Battery Recycling Plant" },
sourcePosition: 'right',
targetPosition: 'left',
position: { x: 500, y: 150 },
className: styles.PlantNode,
},
{
id: '3',
data: { label: 'Nickel' },
sourcePosition: 'right',
targetPosition: 'left',
position: { x: 900, y: 100 },
className: styles.ProductNode,
},
{
id: '4',
data: { label: 'Metal casing' },
sourcePosition: 'right',
targetPosition: 'left',
position: { x: 900, y: 300 },
className: styles.ProductNode,
},
{
id: 'e1-2',
source: '1',
target: '2',
animated: true,
selectable: false,
style: { stroke: "black" },
},
{
id: 'e2-3',
source: '2',
target: '3',
animated: true,
selectable: false,
style: { stroke: "black" },
},
{
id: 'e2-4',
source: '2',
target: '4',
animated: true,
selectable: false,
style: { stroke: "black" },
},
];
const PipelineBlock = () => {
return (
<>
<Section title="Pipeline" />
<Card>
<div className={styles.PipelineBlock}>
<ReactFlow elements={elements}>
<Background />
</ReactFlow>
</div>
<div style={{ textAlign: 'center' }}>
<Button label="Add product" kind="inline" />
<Button label="Add plant" kind="inline" />
</div>
</Card>
</>
)
}
export default PipelineBlock;

View File

@@ -0,0 +1,21 @@
.PipelineBlock {
height: 600px;
}
.PlantNode, .ProductNode {
border-color: rgba(0, 0, 0, 0.8);
color: black;
font-size: 13px;
border-width: 1px;
border-radius: 6px;
box-shadow: 0px 2px 4px -3px black;
width: 100px;
}
.PlantNode {
background-color: #0df;
}
.ProductNode {
background-color: #f6f6f6;
}

142
relog-web/src/PlantBlock.js Normal file
View File

@@ -0,0 +1,142 @@
import Section from './Section'
import Card from './Card'
import Form from './Form'
import TextInputRow from './TextInputRow'
import FileInputRow from './FileInputRow'
import DictInputRow from './DictInputRow'
const PlantBlock = (props) => {
const emissions = {
"CO2": "0.05",
"CH4": "0.01",
"N2O": "0.04",
}
const output = {
"Nickel": "0.5",
"Metal casing": "0.35",
}
return (
<>
<Section title={props.name} />
<Card>
<Form>
<h1>General information</h1>
<FileInputRow
label="Candidate locations"
tooltip="A dictionary mapping the name of the location to a dictionary which describes the site characteristics."
/>
<h1>Inputs & Outputs</h1>
<TextInputRow
label="Input"
tooltip="The name of the product that this plant takes as input. Only one input is accepted per plant."
disabled="disabled"
value="Battery"
/>
<DictInputRow
label="Outputs"
unit="tonne/tonne"
tooltip="A dictionary specifying how many tonnes of each product is produced for each tonnes of input. If the plant does not output anything, this key may be omitted."
value={output}
disableKeys={true}
default="0"
/>
<h1>Capacity & costs</h1>
<TextInputRow
label="Minimum capacity"
unit="tonne"
tooltip="The minimum size of the plant."
default="0"
/>
<TextInputRow
label="Opening cost (min capacity)"
unit="$"
tooltip="The cost to open the plant at minimum capacity."
default="0.00"
/>
<TextInputRow
label="Fixed operating cost (min capacity)"
unit="$"
tooltip="The cost to keep the plant open, even if the plant doesn't process anything."
default="0.00"
/>
<TextInputRow
label="Maximum capacity"
unit="tonne"
tooltip="The maximum size of the plant."
default="0"
/>
<TextInputRow
label="Opening cost (max capacity)"
unit="$"
tooltip="The cost to open a plant of this size."
default="0.00"
/>
<TextInputRow
label="Fixed operating cost (max capacity)"
unit="$"
tooltip="The cost to keep the plant open, even if the plant doesn't process anything."
default="0.00"
/>
<TextInputRow
label="Variable operating cost"
unit="$"
tooltip="The cost that the plant incurs to process each tonne of input."
default="0.00"
/>
<TextInputRow
label="Energy expenditure"
unit="GJ/tonne"
tooltip="The energy required to process 1 tonne of the input."
default="0"
/>
<h1>Storage</h1>
<TextInputRow
label="Storage cost"
unit="$/tonne"
tooltip="The cost to store a tonne of input product for one time period."
default="0.00"
/>
<TextInputRow
label="Storage limit"
unit="tonne"
tooltip="The maximum amount of input product this plant can have in storage at any given time."
default="0"
/>
<h1>Disposal</h1>
<DictInputRow
label="Disposal cost"
unit="$/tonne"
tooltip="The cost to dispose of the product."
value={output}
disableKeys={true}
/>
<DictInputRow
label="Disposal limit"
unit="tonne"
tooltip="The maximum amount that can be disposed of. If an unlimited amount can be disposed, this key may be omitted."
value={output}
disableKeys={true}
/>
<h1>Emissions</h1>
<DictInputRow
label="Emissions"
unit="tonne/tonne"
tooltip="A dictionary mapping the name of each greenhouse gas, produced to process each tonne of input, to the amount of gas produced (in tonne)."
value={emissions}
keyPlaceholder="Emission name"
valuePlaceholder="0"
/>
</Form>
</Card>
</>
)
}
export default PlantBlock;

View File

@@ -0,0 +1,64 @@
import Section from './Section'
import Card from './Card'
import Form from './Form'
import TextInputRow from './TextInputRow'
import FileInputRow from './FileInputRow'
const ProductBlock = (props) => {
return (
<>
<Section title={props.name} />
<Card>
<Form>
<h1>General information</h1>
<FileInputRow
label="Initial amounts"
tooltip="A dictionary mapping the name of each location to its description (see below). If this product is not initially available, this key may be omitted."
/>
<TextInputRow
label="Acquisition cost"
unit="$/tonne"
tooltip="The cost to acquire one tonne of this product from collection centers. Does not apply to plant outputs."
default="0.00"
/>
<h1>Disposal</h1>
<TextInputRow
label="Disposal cost"
unit="$/tonne"
tooltip="The cost to dispose of one tonne of this product at a collection center, without further processing. Does not apply to plant outputs."
default="0"
/>
<TextInputRow
label="Disposal limit"
unit="tonne"
tooltip="The maximum amount of this product that can be disposed of across all collection centers, without further processing."
default="0"
/>
<h1>Transportation</h1>
<TextInputRow
label="Transportation cost"
unit="$/km/tonne"
tooltip="The cost to transport this product."
default="0.00"
/>
<TextInputRow
label="Transportation energy"
unit="J/km/tonne"
default="0"
tooltip="The energy required to transport this product."
/>
<TextInputRow
label="Transportation emissions"
unit="J/km/tonne"
tooltip="A dictionary mapping the name of each greenhouse gas, produced to transport one tonne of this product along one kilometer, to the amount of gas produced (in tonnes)."
default="0"
/>
</Form>
</Card>
</>
)
}
export default ProductBlock;

7
relog-web/src/Section.js Normal file
View File

@@ -0,0 +1,7 @@
import styles from './Section.module.css'
const Section = (props) => {
return <h2 className={styles.Section}>{props.title}</h2>
}
export default Section;

View File

@@ -0,0 +1,6 @@
.Section {
line-height: 36px;
margin: 12px;
font-size: 16px;
font-weight: bold;
}

View File

@@ -0,0 +1,29 @@
import form_styles from './Form.module.css'
import Button from './Button'
const TextInputRow = (props) => {
let unit = "";
if (props.unit) {
unit = <span className={form_styles.FormRow_unit}>({props.unit})</span>
}
let tooltip = "";
if (props.tooltip != undefined) {
tooltip = <Button label="?" kind="inline" tooltip={props.tooltip} />
}
return <div className={form_styles.FormRow}>
<label>
{props.label} {unit}
</label>
<input
type="text"
placeholder={props.default}
disabled={props.disabled}
value={props.value}
/>
{tooltip}
</div>;
}
export default TextInputRow;

48
relog-web/src/index.css Normal file
View File

@@ -0,0 +1,48 @@
:root {
--site-width: 1200px;
--box-border: 1px solid rgba(0, 0, 0, 0.2);
--box-shadow: 0px 2px 4px -3px rgba(0, 0, 0, 0.2);
--border-radius: 4px;
}
html, body {
margin: 0;
padding: 0;
border: 0;
}
body {
background-color: #f6f6f6;
color: rgba(0, 0, 0, 0.95);
}
#content {
max-width: var(--site-width);
min-width: 900px;
margin: 0 auto;
padding: 0 6px;
}
.react-flow__node.selected {
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2) !important;
border-width: 2px !important;
margin-top: -1px !important;
margin-left: -1px !important;
border-radius: 8px !important;
font-weight: bold;
}
.react-flow__handle {
width: 8px !important;
height: 8px !important;
background-color: white !important;
border: 1px solid black !important;
}
.react-flow__handle-right {
right: -5px !important;
}
.react-flow__handle-left {
left: -5px !important;
}

17
relog-web/src/index.js Normal file
View File

@@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Header from './Header';
import InputPage from './InputPage';
import Footer from './Footer'
ReactDOM.render(
<React.StrictMode>
<Header />
<div id="content">
<InputPage />
</div>
<Footer />
</React.StrictMode>,
document.getElementById('root')
);