[UNSTABLE] Saves, edits, deletes work. NEXT: tests

pull/460/head
Rick Carlino 2017-09-19 13:28:19 -05:00
parent 14093aaf40
commit ebf3cb8c18
14 changed files with 194 additions and 110 deletions

View File

@ -97,7 +97,9 @@ unless Rails.env == "production"
executable_id: Sequence.where(device: u.device).order("RANDOM()").first.id,
executable_type: "Sequence")
end
WebcamFeeds::Create.run!(device: u.device,
name: "My Feed 1",
url: "https://nature.nps.gov/air/webcams/parks/yosecam/yose.jpg")
ts = ToolSlots::Create.run!(device: u.device,
tool_id: t.id,
name: "Slot One.",

View File

@ -114,10 +114,12 @@ export function fakePoint(): TaggedGenericPointer {
}
export function fakeWebcamFeed(): TaggedWebcamFeed {
const id = idCounter++;
return fakeResource("webcam_feed", {
id: idCounter++,
id,
created_at: "---",
updated_at: "---",
url: "http://i.imgur.com/iAOUmEB.jpg"
url: "http://i.imgur.com/iAOUmEB.jpg",
name: "wcf #" + id
});
}

View File

@ -228,10 +228,10 @@ const MUST_CONFIRM_LIST: ResourceName[] = [
"images"
];
const confirmationChecker = (resource: TaggedResource) =>
const confirmationChecker = (resource: TaggedResource, force = true) =>
<T>(proceed: () => T): T | undefined => {
if (MUST_CONFIRM_LIST.includes(resource.kind)) {
if (confirm("Are you sure you want to delete this item?")) {
if (!force && confirm("Are you sure you want to delete this item?")) {
return proceed();
} else {
return undefined;

View File

@ -31,7 +31,7 @@ export class Controls extends React.Component<Props, {}> {
disabled={arduinoBusy} />
</Col>
<Col xs={12} sm={6}>
<WebcamPanel />
<WebcamPanel feeds={this.props.feeds} dispatch={this.props.dispatch} />
</Col>
</Row>
</Page>;

View File

@ -94,6 +94,7 @@ export interface ToggleButtonProps {
export interface WebcamFeed {
id: number;
url: string;
name: string;
updated_at: string;
created_at: string;
}

View File

@ -1,31 +1,35 @@
import * as React from "react";
import { Row, Col } from "../ui/index";
import { ToggleButton } from "./toggle_button";
import { KeyValRowProps } from "./key_val_show_row";
interface KeyValRowProps {
label: string;
value: string;
onClick(): void;
disabled: boolean;
interface Props extends KeyValRowProps {
onLabelChange(e: React.ChangeEvent<HTMLInputElement>): void;
onValueChange(e: React.ChangeEvent<HTMLInputElement>): void;
valueType: "number" | "string";
}
/** A row containing two textboxes and a delete button. Useful for maintaining
* lists of things (peripherals, feeds, tools etc). */
export function KeyValEditRow(p: KeyValRowProps) {
const { label, value, disabled, onClick } = p;
export function KeyValEditRow(p: Props) {
return <Row>
<Col xs={4}>
<label>{label}</label>
<Col xs={6}>
<input type="text"
placeholder="Label"
value={p.label}
onChange={p.onLabelChange} />
</Col>
<Col xs={4}>
<p>{label}</p>
<input type={p.valueType}
value={p.value}
placeholder="Pin #"
onChange={p.onValueChange} />
</Col>
<Col xs={4}>
<ToggleButton
toggleValue={value}
toggleAction={onClick}
noYes={false}
disabled={disabled} />
<Col xs={2}>
<button
className="red fb-button"
onClick={p.onClick}>
<i className="fa fa-minus" />
</button>
</Col>
</Row>;
}

View File

@ -0,0 +1,31 @@
import * as React from "react";
import { Row, Col } from "../ui/index";
import { ToggleButton } from "./toggle_button";
export interface KeyValRowProps {
label: string;
value: string;
onClick(): void;
disabled: boolean;
}
/** A row containing two textboxes and a delete button. Useful for maintaining
* lists of things (peripherals, feeds, tools etc). */
export function KeyValShowRow(p: KeyValRowProps) {
const { label, value, disabled, onClick } = p;
return <Row>
<Col xs={4}>
<label>{label}</label>
</Col>
<Col xs={4}>
<p>{label}</p>
</Col>
<Col xs={4}>
<ToggleButton
toggleValue={value}
toggleAction={onClick}
noYes={false}
disabled={disabled} />
</Col>
</Row>;
}

View File

@ -1,42 +1,31 @@
import * as React from "react";
import { Row, Col } from "../../ui/index";
import { destroy, edit } from "../../api/crud";
import { PeripheralFormProps } from "./interfaces";
import { sortResourcesById } from "../../util";
import { KeyValEditRow } from "../key_val_edit_row";
export function PeripheralForm(props: PeripheralFormProps) {
const { dispatch, peripherals } = props;
return <div>
{sortResourcesById(peripherals).map(p => {
return <Row key={p.uuid}>
<Col xs={6}>
<input type="text"
placeholder="Label"
value={p.body.label}
onChange={(e) => {
const { value } = e.currentTarget;
dispatch(edit(p, { label: value }));
}} />
</Col>
<Col xs={4}>
<input type="number"
value={(p.body.pin || "").toString()}
placeholder="Pin #"
onChange={(e) => {
const { value } = e.currentTarget;
const update: Partial<typeof p.body> = { pin: parseInt(value, 10) };
dispatch(edit(p, update));
}} />
</Col>
<Col xs={2}>
<button
className="red fb-button"
onClick={() => { dispatch(destroy(p.uuid)); }}>
<i className="fa fa-minus" />
</button>
</Col>
</Row>;
return <KeyValEditRow
key={p.uuid}
label={p.body.label}
onLabelChange={(e) => {
const { value } = e.currentTarget;
dispatch(edit(p, { label: value }));
}}
value={(p.body.pin || "").toString()}
onValueChange={(e) => {
const { value } = e.currentTarget;
const update: Partial<typeof p.body> = { pin: parseInt(value, 10) };
dispatch(edit(p, update));
}}
onClick={() => { dispatch(destroy(p.uuid)); }}
disabled={false}
valueType="number" />;
})}
</div>;
}

View File

@ -2,14 +2,14 @@ import * as React from "react";
import { pinToggle } from "../../devices/actions";
import { PeripheralListProps } from "./interfaces";
import { sortResourcesById } from "../../util";
import { KeyValEditRow } from "../key_val_edit_row";
import { KeyValShowRow } from "../key_val_show_row";
export function PeripheralList(props: PeripheralListProps) {
const { pins, disabled } = props;
return <div>
{sortResourcesById(props.peripherals).map(p => {
const value = "" + (pins[p.body.pin || -1] || "");
return <KeyValEditRow key={p.uuid}
return <KeyValShowRow key={p.uuid}
label={p.body.label}
value={value || ""}
onClick={() => p.body.pin && pinToggle(p.body.pin)}

View File

@ -1,18 +1,42 @@
import * as React from "react";
import { Widget, WidgetHeader } from "../../ui/index";
import { t } from "i18next";
import { ToolTips } from "../../constants";
import { WebcamPanelProps } from "./interfaces";
import { KeyValEditRow } from "../key_val_edit_row";
import { SpecialStatus } from "../../resources/tagged_resources";
interface Props {
}
interface State {
}
export class WebcamEdit extends React.Component<Props, State> {
state: State = {};
render() {
return <div />;
}
export function Edit(props: WebcamPanelProps) {
const rows = props.feeds.map(wcf => {
return <KeyValEditRow key={wcf.uuid}
onClick={() => props.destroy(wcf)}
onLabelChange={(e) => props.edit(wcf, { name: e.currentTarget.value })}
onValueChange={(e) => props.edit(wcf, { url: e.currentTarget.value })}
disabled={true}
value={wcf.body.url}
label={wcf.body.name}
valueType="string" />;
});
const unsaved = props
.feeds
.filter(x => x.specialStatus === SpecialStatus.DIRTY);
return (
<Widget>
<WidgetHeader title="Edit" helpText={ToolTips.WEBCAM}>
<button
className="fb-button green"
onClick={() => { unsaved.map(x => props.save(x)); }}>
{t("Save")}{unsaved.length > 0 ? "*" : ""}
</button>
<button
className="fb-button gray"
onClick={props.onToggle}>
{t("Edit")}
</button>
</WidgetHeader>
<div className="widget-body">
{rows}
</div>
</Widget>
);
}

View File

@ -1,40 +1,37 @@
import * as React from "react";
import { t } from "i18next";
import { Widget, WidgetHeader } from "../../ui/index";
import { ToolTips } from "../../constants";
import { Show } from "./show";
import { Edit } from "./edit";
import { WebcamPanelProps } from "./interfaces";
import { TaggedWebcamFeed } from "../../resources/tagged_resources";
import { edit, save, destroy } from "../../api/crud";
type S = {
activeMenu: "edit" | "show"
};
type P = {
feeds: TaggedWebcamFeed[];
dispatch: Function;
};
type S = {};
type P = {};
const noop = () => alert("TODO");
export class WebcamPanel extends React.Component<P, S> {
state: S = {};
state: S = { activeMenu: "show" };
childProps = (activeMenu: "edit" | "show"): WebcamPanelProps => {
return {
onToggle: () => this.setState({ activeMenu }),
feeds: this.props.feeds,
edit: (tr, update) => this.props.dispatch(edit(tr, update)),
save: (tr) => { this.props.dispatch(save(tr.uuid)); },
destroy: (tr) => { this.props.dispatch(destroy(tr.uuid)); }
};
}
render() {
return (
<Widget>
<WidgetHeader title="Webcam" helpText={ToolTips.WEBCAM}>
<button
className="fb-button green"
onClick={noop}>
{t("Save")}*
</button>
<button
className="fb-button gray"
onClick={noop}>
{t("Edit")}
</button>
</WidgetHeader>
<div className="widget-body">
<label>{t("Set Webcam URL:")}</label>
<input
type="text"
onChange={noop}
placeholder="https://"
value={undefined}
className="webcam-url-input" />
</div>
</Widget>
);
switch (this.state.activeMenu) {
case "show": return <Show {...this.childProps("edit") } />;
default: return <Edit {...this.childProps("show") } />;
}
}
}

View File

@ -0,0 +1,9 @@
import { TaggedWebcamFeed } from "../../resources/tagged_resources";
export interface WebcamPanelProps {
onToggle(): void;
feeds: TaggedWebcamFeed[];
edit(tr: TaggedWebcamFeed, changes: Partial<typeof tr.body>): void;
save(tr: TaggedWebcamFeed): void;
destroy(tr: TaggedWebcamFeed): void;
}

View File

@ -0,0 +1,34 @@
import * as React from "react";
import { Widget, WidgetHeader } from "../../ui/index";
import { t } from "i18next";
import { ToolTips } from "../../constants";
import { WebcamPanelProps } from "./interfaces";
const noop = () => alert("TODO");
export function Show(props: WebcamPanelProps) {
return (
<Widget>
<WidgetHeader title="Webcam" helpText={ToolTips.WEBCAM}>
<button
className="fb-button green"
onClick={noop}>
{t("Save")}*
</button>
<button
className="fb-button gray"
onClick={props.onToggle}>
{t("Edit")}
</button>
</WidgetHeader>
<div className="widget-body">
<label>{t("Set Webcam URL:")}</label>
<input
type="text"
onChange={noop}
placeholder="https://"
value={undefined}
className="webcam-url-input" />
</div>
</Widget>
);
}

View File

@ -1,9 +0,0 @@
import * as React from "react";
interface WebcamFeedRowProps {
}
export function WebcamFeedRow(props: WebcamFeedRowProps) {
return <div />;
}