[UNSTABLE] Saves, edits, deletes work. NEXT: tests
parent
14093aaf40
commit
ebf3cb8c18
|
@ -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.",
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -94,6 +94,7 @@ export interface ToggleButtonProps {
|
|||
export interface WebcamFeed {
|
||||
id: number;
|
||||
url: string;
|
||||
name: string;
|
||||
updated_at: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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") } />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import * as React from "react";
|
||||
|
||||
interface WebcamFeedRowProps {
|
||||
|
||||
}
|
||||
|
||||
export function WebcamFeedRow(props: WebcamFeedRowProps) {
|
||||
return <div />;
|
||||
}
|
Loading…
Reference in New Issue