diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-07-14 22:23:40 +0200 |
|---|---|---|
| committer | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-07-15 15:55:56 +0200 |
| commit | 803e13b32cf0ff8b496649fb0a4d6e32400e98a4 (patch) | |
| tree | 263a6f9741c5ca0dd64ecf3f7f07b580331aec9d /opendc-web/opendc-web-ui/src/components/modals | |
| parent | e200dbfdc076ac6263c9ac6f9dabdcc475f01d6e (diff) | |
feat(ui): Migrate to PatternFly 4 design framework
This change is a rewrite of the existing OpenDC frontend in order to
migrate to the PatternFly 4 design framework.
PatternFly is used by Red Hat for various computing related services such
as OpenShift, Red Hat Virtualization and Cockpit. Since their design
requirements are very similar to those of OpenDC (modeling computing
services), migrating to PatternFly 4 allows us to re-use design choices
from these services.
See https://www.patternfly.org/v4/ for more information about
PatternFly.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/components/modals')
9 files changed, 445 insertions, 346 deletions
diff --git a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js index 5a95810a..f6e1c98b 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js +++ b/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js @@ -2,11 +2,11 @@ import PropTypes from 'prop-types' import React from 'react' import Modal from './Modal' -function ConfirmationModal({ title, message, show, callback }) { +function ConfirmationModal({ title, message, isOpen, callback }) { return ( <Modal title={title} - show={show} + isOpen={isOpen} onSubmit={() => callback(true)} onCancel={() => callback(false)} submitButtonType="danger" @@ -20,7 +20,7 @@ function ConfirmationModal({ title, message, show, callback }) { ConfirmationModal.propTypes = { title: PropTypes.string.isRequired, message: PropTypes.string.isRequired, - show: PropTypes.bool.isRequired, + isOpen: PropTypes.bool.isRequired, callback: PropTypes.func.isRequired, } diff --git a/opendc-web/opendc-web-ui/src/components/modals/Modal.js b/opendc-web/opendc-web-ui/src/components/modals/Modal.js index 8ab3924c..d4577062 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/Modal.js +++ b/opendc-web/opendc-web-ui/src/components/modals/Modal.js @@ -1,43 +1,27 @@ -import React, { useState, useEffect } from 'react' +import React from 'react' import PropTypes from 'prop-types' -import { Modal as RModal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' +import { Button, Modal as PModal, ModalVariant } from '@patternfly/react-core' -function Modal({ children, title, show, onSubmit, onCancel, submitButtonType, submitButtonText }) { - const [modal, setModal] = useState(show) - - useEffect(() => setModal(show), [show]) - - const toggle = () => setModal(!modal) - const cancel = () => { - if (onCancel() !== false) { - toggle() - } - } - const submit = () => { - if (onSubmit() !== false) { - toggle() - } - } +function Modal({ children, title, isOpen, onSubmit, onCancel, submitButtonType, submitButtonText }) { + const actions = [ + <Button variant={submitButtonType} onClick={onSubmit} key="confirm"> + {submitButtonText} + </Button>, + <Button variant="link" onClick={onCancel} key="cancel"> + Cancel + </Button>, + ] return ( - <RModal isOpen={modal} toggle={cancel}> - <ModalHeader toggle={cancel}>{title}</ModalHeader> - <ModalBody>{children}</ModalBody> - <ModalFooter> - <Button color="secondary" onClick={cancel}> - Close - </Button> - <Button color={submitButtonType} onClick={submit}> - {submitButtonText} - </Button> - </ModalFooter> - </RModal> + <PModal variant={ModalVariant.small} isOpen={isOpen} onClose={onCancel} title={title} actions={actions}> + {children} + </PModal> ) } Modal.propTypes = { title: PropTypes.string.isRequired, - show: PropTypes.bool.isRequired, + isOpen: PropTypes.bool, onSubmit: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, submitButtonType: PropTypes.string, @@ -48,7 +32,7 @@ Modal.propTypes = { Modal.defaultProps = { submitButtonType: 'primary', submitButtonText: 'Save', - show: false, + isOpen: false, } export default Modal diff --git a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js index 6758fdc0..392a729e 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js +++ b/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js @@ -1,31 +1,60 @@ import PropTypes from 'prop-types' -import React, { useRef } from 'react' +import React, { useRef, useState } from 'react' import Modal from './Modal' +import { Form, FormGroup, TextInput } from '@patternfly/react-core' -function TextInputModal({ title, label, show, callback, initialValue }) { +function TextInputModal({ title, label, isOpen, callback, initialValue }) { const textInput = useRef(null) - const onSubmit = () => { - callback(textInput.current.value) + const [isSubmitted, setSubmitted] = useState(false) + const [isValid, setValid] = useState(true) + + const resetState = () => { textInput.current.value = '' + setSubmitted(false) + setValid(false) + } + const onSubmit = (event) => { + const value = textInput.current.value + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + if (!value) { + setValid(false) + return false + } + + callback(value) + resetState() + return true } const onCancel = () => { callback(undefined) - textInput.current.value = '' + resetState() } return ( - <Modal title={title} show={show} onSubmit={onSubmit} onCancel={onCancel}> - <form - onSubmit={(e) => { - e.preventDefault() - onSubmit() - }} - > - <div className="form-group"> - <label className="form-control-label">{label}</label> - <input type="text" className="form-control" ref={textInput} defaultValue={initialValue} /> - </div> - </form> + <Modal title={title} isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormGroup + label={label} + fieldId="text-input" + isRequired + validated={isSubmitted && !isValid ? 'error' : 'default'} + helperTextInvalid="This field cannot be empty" + > + <TextInput + id="text-input" + name="text-input" + isRequired + type="text" + ref={textInput} + defaultValue={initialValue} + /> + </FormGroup> + </Form> </Modal> ) } @@ -33,7 +62,7 @@ function TextInputModal({ title, label, show, callback, initialValue }) { TextInputModal.propTypes = { title: PropTypes.string.isRequired, label: PropTypes.string.isRequired, - show: PropTypes.bool.isRequired, + isOpen: PropTypes.bool.isRequired, callback: PropTypes.func.isRequired, initialValue: PropTypes.string, } diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js new file mode 100644 index 00000000..afe07597 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js @@ -0,0 +1,139 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import Modal from '../Modal' +import { + Form, + FormGroup, + FormSection, + NumberInput, + Select, + SelectGroup, + SelectOption, + SelectVariant, + TextInput, +} from '@patternfly/react-core' +import { METRIC_GROUPS, METRIC_NAMES } from '../../../util/available-metrics' + +const NewPortfolioModal = ({ isOpen, onSubmit: onSubmitUpstream, onCancel: onUpstreamCancel }) => { + const nameInput = useRef(null) + const [repeats, setRepeats] = useState(1) + const [isSelectOpen, setSelectOpen] = useState(false) + const [selectedMetrics, setSelectedMetrics] = useState([]) + + const [isSubmitted, setSubmitted] = useState(false) + const [errors, setErrors] = useState({}) + + const clearState = () => { + setSubmitted(false) + setErrors({}) + nameInput.current.value = '' + setRepeats(1) + setSelectOpen(false) + setSelectedMetrics([]) + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + if (!name) { + setErrors({ name: true }) + return false + } else { + onSubmitUpstream(name, { enabledMetrics: selectedMetrics, repeatsPerScenario: repeats }) + } + + clearState() + return false + } + const onCancel = () => { + onUpstreamCancel() + clearState() + } + + const onSelect = (event, selection) => { + if (selectedMetrics.includes(selection)) { + setSelectedMetrics((metrics) => metrics.filter((item) => item !== selection)) + } else { + setSelectedMetrics((metrics) => [...metrics, selection]) + } + } + + return ( + <Modal title="New Portfolio" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormSection> + <FormGroup + label="Name" + fieldId="name" + isRequired + validated={isSubmitted && errors.name ? 'error' : 'default'} + helperTextInvalid="This field cannot be empty" + > + <TextInput + name="name" + id="name" + type="text" + isRequired + ref={nameInput} + placeholder="My Portfolio" + /> + </FormGroup> + </FormSection> + <FormSection title="Targets" titleElement="h4"> + <FormGroup label="Metrics" fieldId="metrics"> + <Select + variant={SelectVariant.typeaheadMulti} + typeAheadAriaLabel="Select a metric" + onToggle={() => setSelectOpen(!isSelectOpen)} + onSelect={onSelect} + onClear={() => setSelectedMetrics([])} + selections={selectedMetrics} + isOpen={isSelectOpen} + placeholderText="Select a metric" + menuAppendTo="parent" + maxHeight="300px" + chipGroupProps={{ numChips: 1 }} + isGrouped + > + {Object.entries(METRIC_GROUPS).map(([group, metrics]) => ( + <SelectGroup label={group} key={group}> + {metrics.map((metric) => ( + <SelectOption key={metric} value={metric}> + {METRIC_NAMES[metric]} + </SelectOption> + ))} + </SelectGroup> + ))} + </Select> + </FormGroup> + <FormGroup label="Repeats per Scenario" fieldId="repeats"> + <NumberInput + id="repeats" + inputName="repeats" + type="number" + value={repeats} + onChange={(e) => setRepeats(Number(e.target.value))} + onPlus={() => setRepeats((r) => r + 1)} + onMinus={() => setRepeats((r) => r - 1)} + min={1} + /> + </FormGroup> + </FormSection> + </Form> + </Modal> + ) +} + +NewPortfolioModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewPortfolioModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js deleted file mode 100644 index 3c6b8724..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js +++ /dev/null @@ -1,78 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef } from 'react' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import Modal from '../Modal' -import { AVAILABLE_METRICS, METRIC_NAMES } from '../../../util/available-metrics' - -const NewPortfolioModalComponent = ({ show, callback }) => { - const form = useRef(null) - const textInput = useRef(null) - const repeatsInput = useRef(null) - const metricCheckboxes = useRef({}) - - const onSubmit = () => { - if (form.current.reportValidity()) { - callback(textInput.current.value, { - enabledMetrics: AVAILABLE_METRICS.filter((metric) => metricCheckboxes.current[metric].checked), - repeatsPerScenario: parseInt(repeatsInput.current.value), - }) - - return true - } else { - return false - } - } - const onCancel = () => callback(undefined) - - return ( - <Modal title="New Portfolio" show={show} onSubmit={onSubmit} onCancel={onCancel}> - <Form - onSubmit={(e) => { - e.preventDefault() - this.onSubmit() - }} - innerRef={form} - > - <FormGroup> - <Label for="name">Name</Label> - <Input name="name" type="text" required innerRef={textInput} placeholder="My Portfolio" /> - </FormGroup> - <h4>Targets</h4> - <h5>Metrics</h5> - <FormGroup> - {AVAILABLE_METRICS.map((metric) => ( - <FormGroup check key={metric}> - <Label for={metric} check> - <Input - name={metric} - type="checkbox" - innerRef={(ref) => (metricCheckboxes.current[metric] = ref)} - /> - {METRIC_NAMES[metric]} - </Label> - </FormGroup> - ))} - </FormGroup> - <FormGroup> - <Label for="repeats">Repeats per scenario</Label> - <Input - name="repeats" - type="number" - required - innerRef={repeatsInput} - defaultValue="1" - min="1" - step="1" - /> - </FormGroup> - </Form> - </Modal> - ) -} - -NewPortfolioModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, -} - -export default NewPortfolioModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js new file mode 100644 index 00000000..94d0d424 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js @@ -0,0 +1,159 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import { Portfolio } from '../../../shapes' +import Modal from '../Modal' +import { + Checkbox, + Form, + FormGroup, + FormSection, + FormSelect, + FormSelectOption, + NumberInput, + TextInput, +} from '@patternfly/react-core' +import { useSchedulers, useTraces } from '../../../data/experiments' +import { useProjectTopologies } from '../../../data/topology' +import { usePortfolio } from '../../../data/project' + +const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { + const { data: portfolio } = usePortfolio(portfolioId) + const { data: topologies = [] } = useProjectTopologies(portfolio?.projectId) + const { data: traces = [] } = useTraces() + const { data: schedulers = [] } = useSchedulers() + + const [isSubmitted, setSubmitted] = useState(false) + const [traceLoad, setTraceLoad] = useState(100) + const [trace, setTrace] = useState(undefined) + const [topology, setTopology] = useState(undefined) + const [scheduler, setScheduler] = useState(undefined) + const [failuresEnabled, setFailuresEnabled] = useState(false) + const [opPhenEnabled, setOpPhenEnabled] = useState(false) + const nameInput = useRef(null) + + const resetState = () => { + setSubmitted(false) + setTraceLoad(100) + setTrace(undefined) + setTopology(undefined) + setScheduler(undefined) + setFailuresEnabled(false) + setOpPhenEnabled(false) + nameInput.current.value = '' + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + onSubmitUpstream( + name, + portfolio._id, + { + traceId: trace || traces[0]._id, + loadSamplingFraction: traceLoad / 100, + }, + { + topologyId: topology || topologies[0]._id, + }, + { + failuresEnabled, + performanceInterferenceEnabled: opPhenEnabled, + schedulerName: scheduler || schedulers[0].name, + } + ) + + resetState() + return true + } + const onCancel = () => { + onCancelUpstream() + resetState() + } + + return ( + <Modal title="New Scenario" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormGroup label="Name" fieldId="name" isRequired> + <TextInput + id="name" + name="name" + type="text" + isDisabled={portfolio?.scenarioIds?.length === 0} + defaultValue={portfolio?.scenarioIds?.length === 0 ? 'Base scenario' : ''} + ref={nameInput} + /> + </FormGroup> + <FormSection title="Workload"> + <FormGroup label="Trace" fieldId="trace" isRequired> + <FormSelect id="trace" name="trace" value={trace} onChange={setTrace}> + {traces.map((trace) => ( + <FormSelectOption value={trace._id} key={trace._id} label={trace.name} /> + ))} + </FormSelect> + </FormGroup> + <FormGroup label="Load Sampling Fraction" fieldId="trace-load" isRequired> + <NumberInput + name="trace-load" + type="number" + min={0} + max={100} + value={traceLoad} + onMinus={() => setTraceLoad((load) => load - 1)} + onPlus={() => setTraceLoad((load) => load + 1)} + onChange={(e) => setTraceLoad(Number(e.target.value))} + unit="%" + /> + </FormGroup> + </FormSection> + <FormSection title="Topology"> + <FormGroup label="Topology" fieldId="topology" isRequired> + <FormSelect id="topology" name="topology" value={topology} onChange={setTopology}> + {topologies.map((topology) => ( + <FormSelectOption value={topology._id} key={topology._id} label={topology.name} /> + ))} + </FormSelect> + </FormGroup> + + <FormGroup label="Scheduler" fieldId="scheduler" isRequired> + <FormSelect id="scheduler" name="scheduler" value={scheduler} onChange={setScheduler}> + {schedulers.map((scheduler) => ( + <FormSelectOption value={scheduler.name} key={scheduler.name} label={scheduler.name} /> + ))} + </FormSelect> + </FormGroup> + </FormSection> + <FormSection title="Operational Phenomena"> + <Checkbox + label="Failures" + id="failures" + name="failures" + isChecked={failuresEnabled} + onChange={() => setFailuresEnabled((e) => !e)} + /> + <Checkbox + label="Performance Interference" + id="perf-interference" + name="perf-interference" + isChecked={opPhenEnabled} + onChange={() => setOpPhenEnabled((e) => !e)} + /> + </FormSection> + </Form> + </Modal> + ) +} + +NewScenarioModal.propTypes = { + portfolioId: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewScenarioModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js deleted file mode 100644 index 782812ac..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js +++ /dev/null @@ -1,144 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef } from 'react' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import { Scheduler, Topology, Trace } from '../../../shapes' -import Modal from '../Modal' - -const NewScenarioModalComponent = ({ - show, - callback, - currentPortfolioId, - currentPortfolioScenarioIds, - traces, - topologies, - schedulers, -}) => { - const form = useRef(null) - const textInput = useRef(null) - const traceSelect = useRef(null) - const traceLoadInput = useRef(null) - const topologySelect = useRef(null) - const failuresCheckbox = useRef(null) - const performanceInterferenceCheckbox = useRef(null) - const schedulerSelect = useRef(null) - - const onSubmit = () => { - if (!form.current.reportValidity()) { - return false - } - callback( - textInput.current.value, - currentPortfolioId, - { - traceId: traceSelect.current.value, - loadSamplingFraction: parseFloat(traceLoadInput.current.value), - }, - { - topologyId: topologySelect.current.value, - }, - { - failuresEnabled: failuresCheckbox.current.checked, - performanceInterferenceEnabled: performanceInterferenceCheckbox.current.checked, - schedulerName: schedulerSelect.current.value, - } - ) - return true - } - const onCancel = () => { - callback(undefined) - } - - return ( - <Modal title="New Scenario" show={show} onSubmit={onSubmit} onCancel={onCancel}> - <Form - onSubmit={(e) => { - e.preventDefault() - onSubmit() - }} - innerRef={form} - > - <FormGroup> - <Label for="name">Name</Label> - <Input - name="name" - type="text" - required - disabled={currentPortfolioScenarioIds.length === 0} - defaultValue={currentPortfolioScenarioIds.length === 0 ? 'Base scenario' : ''} - innerRef={textInput} - /> - </FormGroup> - <h4>Trace</h4> - <FormGroup> - <Label for="trace">Trace</Label> - <Input name="trace" type="select" innerRef={traceSelect}> - {traces.map((trace) => ( - <option value={trace._id} key={trace._id}> - {trace.name} - </option> - ))} - </Input> - </FormGroup> - <FormGroup> - <Label for="trace-load">Load sampling fraction</Label> - <Input - name="trace-load" - type="number" - innerRef={traceLoadInput} - required - defaultValue="1" - min="0" - max="1" - step="0.1" - /> - </FormGroup> - <h4>Topology</h4> - <div className="form-group"> - <Label for="topology">Topology</Label> - <Input name="topology" type="select" innerRef={topologySelect}> - {topologies.map((topology) => ( - <option value={topology._id} key={topology._id}> - {topology.name} - </option> - ))} - </Input> - </div> - <h4>Operational Phenomena</h4> - <FormGroup check> - <Label check for="failures"> - <Input type="checkbox" name="failures" innerRef={failuresCheckbox} />{' '} - <span className="ml-2">Enable failures</span> - </Label> - </FormGroup> - <FormGroup check> - <Label check for="perf-interference"> - <Input type="checkbox" name="perf-interference" innerRef={performanceInterferenceCheckbox} />{' '} - <span className="ml-2">Enable performance interference</span> - </Label> - </FormGroup> - <FormGroup> - <Label for="scheduler">Scheduler</Label> - <Input name="scheduler" type="select" innerRef={schedulerSelect}> - {schedulers.map((scheduler) => ( - <option value={scheduler.name} key={scheduler.name}> - {scheduler.name} - </option> - ))} - </Input> - </FormGroup> - </Form> - </Modal> - ) -} - -NewScenarioModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - currentPortfolioId: PropTypes.string.isRequired, - currentPortfolioScenarioIds: PropTypes.arrayOf(PropTypes.string), - traces: PropTypes.arrayOf(Trace), - topologies: PropTypes.arrayOf(Topology), - schedulers: PropTypes.arrayOf(Scheduler), - callback: PropTypes.func.isRequired, -} - -export default NewScenarioModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js new file mode 100644 index 00000000..49952aec --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js @@ -0,0 +1,81 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import Modal from '../Modal' +import { Form, FormGroup, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core' +import { useProjectTopologies } from '../../../data/topology' + +const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { + const nameInput = useRef(null) + const [isSubmitted, setSubmitted] = useState(false) + const [originTopology, setOriginTopology] = useState(-1) + const [errors, setErrors] = useState({}) + + const { data: topologies = [] } = useProjectTopologies(projectId) + + const clearState = () => { + nameInput.current.value = '' + setSubmitted(false) + setOriginTopology(-1) + setErrors({}) + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + if (!name) { + setErrors({ name: true }) + return false + } else if (originTopology === -1) { + onSubmitUpstream(name) + } else { + onSubmitUpstream(name, originTopology) + } + + clearState() + return true + } + + const onCancel = () => { + onCancelUpstream() + clearState() + } + + return ( + <Modal title="New Topology" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormGroup + label="Name" + fieldId="name" + isRequired + validated={isSubmitted && errors.name ? 'error' : 'default'} + helperTextInvalid="This field cannot be empty" + > + <TextInput id="name" name="name" type="text" isRequired ref={nameInput} /> + </FormGroup> + <FormGroup label="Topology to duplicate" fieldId="origin" isRequired> + <FormSelect id="origin" name="origin" value={originTopology} onChange={setOriginTopology}> + <FormSelectOption value={-1} key={-1} label="None - start from scratch" /> + {topologies.map((topology) => ( + <FormSelectOption value={topology._id} key={topology._id} label={topology.name} /> + ))} + </FormSelect> + </FormGroup> + </Form> + </Modal> + ) +} + +NewTopologyModal.propTypes = { + projectId: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewTopologyModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js deleted file mode 100644 index f06fe797..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js +++ /dev/null @@ -1,71 +0,0 @@ -import PropTypes from 'prop-types' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import React, { useRef } from 'react' -import { Topology } from '../../../shapes' -import Modal from '../Modal' - -const NewTopologyModalComponent = ({ show, onCreateTopology, onDuplicateTopology, onCancel, topologies }) => { - const form = useRef(null) - const textInput = useRef(null) - const originTopology = useRef(null) - - const onCreate = () => { - onCreateTopology(textInput.current.value) - } - - const onDuplicate = () => { - onDuplicateTopology(textInput.current.value, originTopology.current.value) - } - - const onSubmit = () => { - if (!form.current.reportValidity()) { - return false - } else if (originTopology.current.selectedIndex === 0) { - onCreate() - } else { - onDuplicate() - } - - return true - } - - return ( - <Modal title="New Topology" show={show} onSubmit={onSubmit} onCancel={onCancel}> - <Form - onSubmit={(e) => { - e.preventDefault() - onSubmit() - }} - innerRef={form} - > - <FormGroup> - <Label for="name">Name</Label> - <Input name="name" type="text" required innerRef={textInput} /> - </FormGroup> - <FormGroup> - <Label for="origin">Topology to duplicate</Label> - <Input name="origin" type="select" innerRef={originTopology}> - <option value={-1} key={-1}> - None - start from scratch - </option> - {topologies.map((topology) => ( - <option value={topology._id} key={topology._id}> - {topology.name} - </option> - ))} - </Input> - </FormGroup> - </Form> - </Modal> - ) -} - -NewTopologyModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - topologies: PropTypes.arrayOf(Topology), - onCreateTopology: PropTypes.func.isRequired, - onDuplicateTopology: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, -} - -export default NewTopologyModalComponent |
