Alex Mikhalev
7 years ago
25 changed files with 1063 additions and 200 deletions
@ -1,57 +0,0 @@ |
|||||||
import * as classNames from "classnames"; |
|
||||||
import * as React from "react"; |
|
||||||
import { Input, InputProps } from "semantic-ui-react"; |
|
||||||
|
|
||||||
import { Duration } from "@common/Duration"; |
|
||||||
|
|
||||||
export default class DurationInput extends React.Component<{ |
|
||||||
duration: Duration, |
|
||||||
onDurationChange: (newDuration: Duration) => void, |
|
||||||
className?: string, |
|
||||||
}> { |
|
||||||
render() { |
|
||||||
const duration = this.props.duration; |
|
||||||
const className = classNames("field", "durationInput", this.props.className); |
|
||||||
// const editing = this.props.onDurationChange != null;
|
|
||||||
return ( |
|
||||||
<div className={className}> |
|
||||||
<label>Duration</label> |
|
||||||
<div className="ui two fields"> |
|
||||||
<Input |
|
||||||
type="number" |
|
||||||
className="field durationInput--minutes" |
|
||||||
value={duration.minutes} |
|
||||||
onChange={this.onMinutesChange} |
|
||||||
label="M" |
|
||||||
labelPosition="right" |
|
||||||
/> |
|
||||||
<Input |
|
||||||
type="number" |
|
||||||
className="field durationInput--seconds" |
|
||||||
value={duration.seconds} |
|
||||||
onChange={this.onSecondsChange} |
|
||||||
max="60" |
|
||||||
label="S" |
|
||||||
labelPosition="right" |
|
||||||
/> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
private onMinutesChange: InputProps["onChange"] = (e, { value }) => { |
|
||||||
if (isNaN(Number(value))) { |
|
||||||
return; |
|
||||||
} |
|
||||||
const newMinutes = parseInt(value, 10); |
|
||||||
this.props.onDurationChange(this.props.duration.withMinutes(newMinutes)); |
|
||||||
} |
|
||||||
|
|
||||||
private onSecondsChange: InputProps["onChange"] = (e, { value }) => { |
|
||||||
if (isNaN(Number(value))) { |
|
||||||
return; |
|
||||||
} |
|
||||||
const newSeconds = parseInt(value, 10); |
|
||||||
this.props.onDurationChange(this.props.duration.withSeconds(newSeconds)); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,74 @@ |
|||||||
|
import * as classNames from "classnames"; |
||||||
|
import * as React from "react"; |
||||||
|
import { Form, Input, InputProps } from "semantic-ui-react"; |
||||||
|
|
||||||
|
import { Duration } from "@common/Duration"; |
||||||
|
|
||||||
|
export default class DurationView extends React.Component<{ |
||||||
|
label?: string, |
||||||
|
inline?: boolean, |
||||||
|
duration: Duration, |
||||||
|
onDurationChange?: (newDuration: Duration) => void, |
||||||
|
className?: string, |
||||||
|
}> { |
||||||
|
render() { |
||||||
|
const { duration, label, inline, onDurationChange } = this.props; |
||||||
|
const className = classNames("durationInput", this.props.className); |
||||||
|
if (onDurationChange) { |
||||||
|
return ( |
||||||
|
<React.Fragment> |
||||||
|
<Form.Field inline={inline}> |
||||||
|
{label && <label>{label}</label>} |
||||||
|
<div className="durationInputs"> |
||||||
|
<Input |
||||||
|
type="number" |
||||||
|
className="durationInput minutes" |
||||||
|
value={duration.minutes} |
||||||
|
onChange={this.onMinutesChange} |
||||||
|
label="M" |
||||||
|
labelPosition="right" |
||||||
|
onWheel={this.onWheel} |
||||||
|
/> |
||||||
|
<Input |
||||||
|
type="number" |
||||||
|
className="durationInput seconds" |
||||||
|
value={duration.seconds} |
||||||
|
onChange={this.onSecondsChange} |
||||||
|
max="60" |
||||||
|
label="S" |
||||||
|
labelPosition="right" |
||||||
|
onWheel={this.onWheel} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</Form.Field> |
||||||
|
</React.Fragment> |
||||||
|
); |
||||||
|
} else { |
||||||
|
return ( |
||||||
|
<span className={className}> |
||||||
|
{label && <label>{label}</label>} {duration.minutes}M {duration.seconds}S |
||||||
|
</span> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private onMinutesChange: InputProps["onChange"] = (e, { value }) => { |
||||||
|
if (!this.props.onDurationChange || isNaN(Number(value))) { |
||||||
|
return; |
||||||
|
} |
||||||
|
const newMinutes = Number(value); |
||||||
|
this.props.onDurationChange(this.props.duration.withMinutes(newMinutes)); |
||||||
|
} |
||||||
|
|
||||||
|
private onSecondsChange: InputProps["onChange"] = (e, { value }) => { |
||||||
|
if (!this.props.onDurationChange || isNaN(Number(value))) { |
||||||
|
return; |
||||||
|
} |
||||||
|
const newSeconds = Number(value); |
||||||
|
this.props.onDurationChange(this.props.duration.withSeconds(newSeconds)); |
||||||
|
} |
||||||
|
|
||||||
|
private onWheel = () => { |
||||||
|
// do nothing
|
||||||
|
} |
||||||
|
} |
@ -1,19 +1,94 @@ |
|||||||
|
import classNames = require("classnames"); |
||||||
|
import { observer } from "mobx-react"; |
||||||
import * as React from "react"; |
import * as React from "react"; |
||||||
|
import { Form, List } from "semantic-ui-react"; |
||||||
|
|
||||||
|
import { DurationView, SectionChooser } from "@app/components/index"; |
||||||
import { Duration } from "@common/Duration"; |
import { Duration } from "@common/Duration"; |
||||||
import { ProgramItem, Section} from "@common/sprinklersRpc"; |
import { ProgramItem, Section } from "@common/sprinklersRpc"; |
||||||
|
|
||||||
export default function ProgramSequenceView({ sequence, sections }: { |
@observer |
||||||
sequence: ProgramItem[], sections: Section[], |
class ProgramSequenceItem extends React.Component<{ |
||||||
}) { |
sequenceItem: ProgramItem, sections: Section[], onChange?: (newItem: ProgramItem) => void, |
||||||
const sequenceItems = sequence.map((item, index) => { |
}> { |
||||||
const section = sections[item.section]; |
renderContent() { |
||||||
const duration = Duration.fromSeconds(item.duration); |
const { sequenceItem, sections } = this.props; |
||||||
|
const editing = this.props.onChange != null; |
||||||
|
const section = sections[sequenceItem.section]; |
||||||
|
const duration = Duration.fromSeconds(sequenceItem.duration); |
||||||
|
|
||||||
|
if (editing) { |
||||||
|
return ( |
||||||
|
<Form.Group inline> |
||||||
|
<SectionChooser |
||||||
|
label="Section" |
||||||
|
inline |
||||||
|
sections={sections} |
||||||
|
value={section} |
||||||
|
onChange={this.onSectionChange} |
||||||
|
/> |
||||||
|
<DurationView |
||||||
|
label="Duration" |
||||||
|
inline |
||||||
|
duration={duration} |
||||||
|
onDurationChange={this.onDurationChange} |
||||||
|
/> |
||||||
|
</Form.Group> |
||||||
|
); |
||||||
|
} else { |
||||||
|
return ( |
||||||
|
<React.Fragment> |
||||||
|
<List.Header>{section.toString()}</List.Header> |
||||||
|
<List.Description>for {duration.toString()}</List.Description> |
||||||
|
</React.Fragment> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
return ( |
return ( |
||||||
<li key={index}> |
<List.Item> |
||||||
<em>"{section.name}"</em> for {duration.toString()} |
<List.Icon name="caret right"/> |
||||||
</li> |
<List.Content>{this.renderContent()}</List.Content> |
||||||
|
</List.Item> |
||||||
); |
); |
||||||
|
} |
||||||
|
|
||||||
|
private onSectionChange = (newSection: Section) => { |
||||||
|
if (!this.props.onChange) { |
||||||
|
return; |
||||||
|
} |
||||||
|
this.props.onChange(new ProgramItem({ |
||||||
|
...this.props.sequenceItem, section: newSection.id, |
||||||
|
})); |
||||||
|
} |
||||||
|
|
||||||
|
private onDurationChange = (newDuration: Duration) => { |
||||||
|
if (!this.props.onChange) { |
||||||
|
return; |
||||||
|
} |
||||||
|
this.props.onChange(new ProgramItem({ |
||||||
|
...this.props.sequenceItem, duration: newDuration.toSeconds(), |
||||||
|
})); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@observer |
||||||
|
export default class ProgramSequenceView extends React.Component<{ |
||||||
|
sequence: ProgramItem[], sections: Section[], editing?: boolean, |
||||||
|
}> { |
||||||
|
render() { |
||||||
|
const { sequence, sections } = this.props; |
||||||
|
const editing = this.props.editing || false; |
||||||
|
const className = classNames("programSequence", { editing }); |
||||||
|
const sequenceItems = sequence.map((item, index) => { |
||||||
|
const onChange = editing ? (newItem: ProgramItem) => this.changeItem(newItem, index) : undefined; |
||||||
|
return <ProgramSequenceItem sequenceItem={item} sections={sections} key={index} onChange={onChange}/>; |
||||||
}); |
}); |
||||||
return <ul>{sequenceItems}</ul>; |
return <List className={className}>{sequenceItems}</List>; |
||||||
|
} |
||||||
|
|
||||||
|
private changeItem = (newItem: ProgramItem, index: number) => { |
||||||
|
this.props.sequence[index] = newItem; |
||||||
|
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,47 @@ |
|||||||
|
import { computed } from "mobx"; |
||||||
|
import { observer } from "mobx-react"; |
||||||
|
import * as React from "react"; |
||||||
|
import { DropdownItemProps, DropdownProps, Form } from "semantic-ui-react"; |
||||||
|
|
||||||
|
import { Section } from "@common/sprinklersRpc"; |
||||||
|
|
||||||
|
@observer |
||||||
|
export default class SectionChooser extends React.Component<{ |
||||||
|
label?: string, |
||||||
|
inline?: boolean, |
||||||
|
sections: Section[], |
||||||
|
value?: Section, |
||||||
|
onChange?: (section: Section) => void, |
||||||
|
}> { |
||||||
|
render() { |
||||||
|
const { label, inline, sections, value, onChange } = this.props; |
||||||
|
let section = (value == null) ? "" : sections.indexOf(value); |
||||||
|
section = (section === -1) ? "" : section; |
||||||
|
const onSectionChange = (onChange == null) ? undefined : this.onSectionChange; |
||||||
|
if (onChange == null) { |
||||||
|
return <React.Fragment>{label || ""} '{value ? value.toString() : ""}'</React.Fragment>; |
||||||
|
} |
||||||
|
return ( |
||||||
|
<Form.Select |
||||||
|
label={label} |
||||||
|
inline={inline} |
||||||
|
placeholder="Section" |
||||||
|
options={this.sectionOptions} |
||||||
|
value={section} |
||||||
|
onChange={onSectionChange} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
private onSectionChange = (e: React.SyntheticEvent<HTMLElement>, v: DropdownProps) => { |
||||||
|
this.props.onChange!(this.props.sections[v.value as number]); |
||||||
|
} |
||||||
|
|
||||||
|
@computed |
||||||
|
private get sectionOptions(): DropdownItemProps[] { |
||||||
|
return this.props.sections.map((s, i) => ({ |
||||||
|
text: s ? `${s.id}: ${s.name}` : null, |
||||||
|
value: i, |
||||||
|
})); |
||||||
|
} |
||||||
|
} |
After Width: | Height: | Size: 955 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
Loading…
Reference in new issue