farmware style updates
parent
a6e0b06e44
commit
7a7aef9a2d
|
@ -30,6 +30,12 @@ input[type=time] {
|
|||
color: #fff;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
button {
|
||||
margin: 0.6rem 0 0 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.all-content-wrapper {
|
||||
margin: 0 auto;
|
||||
padding: 3rem 3rem 0;
|
||||
|
|
|
@ -1,54 +1,53 @@
|
|||
.image-flipper {
|
||||
margin-top: 2rem;
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-flipper-image {
|
||||
max-width: 100%;
|
||||
margin: auto;
|
||||
max-height: 650px;
|
||||
max-width: 100%;
|
||||
margin: auto;
|
||||
max-height: 650px;
|
||||
}
|
||||
|
||||
.image-flipper-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.image-flipper-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.is-loaded-false {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.is-loaded-true {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.no-flipper-image-container {
|
||||
p {
|
||||
position: absolute;
|
||||
text-shadow: 0 0 25px rgba(0, 0, 0, 1), 0 0 25px rgba(0, 0, 0, 1);
|
||||
color: $white;
|
||||
font-size: 1.8rem;
|
||||
text-align: center;
|
||||
padding: 10% 10rem 0;
|
||||
line-height: 2.4rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
p {
|
||||
position: absolute;
|
||||
text-shadow: 0 0 25px rgba(0, 0, 0, 1), 0 0 25px rgba(0, 0, 0, 1);
|
||||
color: $white;
|
||||
font-size: 1.8rem;
|
||||
text-align: center;
|
||||
padding: 10% 10rem 0;
|
||||
line-height: 2.4rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
|
@ -25,32 +25,4 @@
|
|||
margin-right: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.weed-detector-meta {
|
||||
background: $dark_gray;
|
||||
margin-left: -14px;
|
||||
margin-right: -14px;
|
||||
padding: 0.3rem 2rem 0;
|
||||
margin-bottom: -2rem;
|
||||
margin-top: 20px;
|
||||
label {
|
||||
font-weight: normal;
|
||||
margin-right: 1rem;
|
||||
color: $white;
|
||||
}
|
||||
span {
|
||||
font-weight: bold;
|
||||
color: $white;
|
||||
}
|
||||
.created-at {
|
||||
display: inline-block;
|
||||
}
|
||||
.meta-coordinates {
|
||||
float: right;
|
||||
display: flex;
|
||||
.coordinate:not(:last-of-type) {
|
||||
margin-right: 1.8rem;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
.widget-wrapper {
|
||||
box-shadow: 0px 0px 10px $gray;
|
||||
margin-bottom: 3rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.widget-header {
|
||||
|
@ -57,13 +58,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.widget-body {
|
||||
background: $off_white;
|
||||
border-color: $light_gray;
|
||||
color: $dark_gray;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.help-icon:hover .help-text,
|
||||
.help-icon:active .help-text {
|
||||
opacity: 100;
|
||||
|
@ -71,4 +65,19 @@
|
|||
max-height: 200px;
|
||||
transition: 0.5s;
|
||||
transition-delay: 0.5s;
|
||||
}
|
||||
|
||||
.widget-body {
|
||||
background: $off_white;
|
||||
border-color: $light_gray;
|
||||
color: $dark_gray;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.widget-footer {
|
||||
background: $dark_gray;
|
||||
padding: 0.5rem 1rem 0;
|
||||
* {
|
||||
color: $white;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from "react";
|
||||
import * as _ from "lodash";
|
||||
import { t } from "i18next";
|
||||
import { devices } from "../device";
|
||||
import { FWProps, FWState } from "./interfaces";
|
||||
|
@ -13,7 +14,6 @@ import {
|
|||
Col
|
||||
} from "../ui";
|
||||
import { betterCompact } from "../util";
|
||||
import * as _ from "lodash";
|
||||
|
||||
export class FarmwarePanel extends React.Component<FWProps, Partial<FWState>> {
|
||||
constructor() {
|
||||
|
@ -78,70 +78,75 @@ export class FarmwarePanel extends React.Component<FWProps, Partial<FWState>> {
|
|||
}
|
||||
|
||||
render() {
|
||||
return <Widget className="farmware-widget">
|
||||
<WidgetHeader title="Farmware" helpText={ToolTips.FARMWARE}>
|
||||
</WidgetHeader>
|
||||
<WidgetBody>
|
||||
<MustBeOnline fallback="Not available when FarmBot is offline."
|
||||
status={this.props.syncStatus}
|
||||
lockOpen={process.env.NODE_ENV !== "production"}>
|
||||
<Row>
|
||||
<fieldset>
|
||||
<Col xs={12}>
|
||||
<input type="url"
|
||||
placeholder={"https://...."}
|
||||
value={this.state.packageUrl || ""}
|
||||
onChange={(e) => {
|
||||
this.setState({ packageUrl: e.currentTarget.value });
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<button
|
||||
className="fb-button green"
|
||||
onClick={this.install}
|
||||
>
|
||||
{t("Install")}
|
||||
</button>
|
||||
</Col>
|
||||
</fieldset>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<DeprecatedFBSelect list={this.fwList()}
|
||||
onChange={(x) => {
|
||||
let selectedFarmware = x.value;
|
||||
if (_.isString(selectedFarmware)) {
|
||||
this.setState({ selectedFarmware });
|
||||
} else {
|
||||
throw new Error(`Bad farmware UUID: ${x.value}`);
|
||||
}
|
||||
}}
|
||||
placeholder="Installed Farmware Packages" />
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<button
|
||||
className="fb-button red"
|
||||
onClick={this.remove}
|
||||
>
|
||||
{t("Remove")}
|
||||
</button>
|
||||
<button
|
||||
className="fb-button yellow"
|
||||
onClick={this.update}
|
||||
>
|
||||
{t("Update")}
|
||||
</button>
|
||||
<button
|
||||
className="fb-button green"
|
||||
onClick={this.run}
|
||||
>
|
||||
{t("Run")}
|
||||
</button>
|
||||
</Col>
|
||||
</Row>
|
||||
</MustBeOnline>
|
||||
</WidgetBody>
|
||||
</Widget>;
|
||||
return (
|
||||
<Widget className="farmware-widget">
|
||||
<WidgetHeader title="Farmware" helpText={ToolTips.FARMWARE} />
|
||||
<WidgetBody>
|
||||
<MustBeOnline
|
||||
fallback="Not available when FarmBot is offline."
|
||||
status={this.props.syncStatus}
|
||||
lockOpen={process.env.NODE_ENV !== "production"}
|
||||
>
|
||||
<Row>
|
||||
<fieldset>
|
||||
<Col xs={12}>
|
||||
<input type="url"
|
||||
placeholder={"https://...."}
|
||||
value={this.state.packageUrl || ""}
|
||||
onChange={(e) => {
|
||||
this.setState({ packageUrl: e.currentTarget.value });
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<button
|
||||
className="fb-button green"
|
||||
onClick={this.install}
|
||||
>
|
||||
{t("Install")}
|
||||
</button>
|
||||
</Col>
|
||||
</fieldset>
|
||||
</Row>
|
||||
<Row>
|
||||
<fieldset>
|
||||
<Col xs={12}>
|
||||
<DeprecatedFBSelect list={this.fwList()}
|
||||
onChange={(x) => {
|
||||
let selectedFarmware = x.value;
|
||||
if (_.isString(selectedFarmware)) {
|
||||
this.setState({ selectedFarmware });
|
||||
} else {
|
||||
throw new Error(`Bad farmware UUID: ${x.value}`);
|
||||
}
|
||||
}}
|
||||
placeholder="Installed Farmware Packages" />
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<button
|
||||
className="fb-button red"
|
||||
onClick={this.remove}
|
||||
>
|
||||
{t("Remove")}
|
||||
</button>
|
||||
<button
|
||||
className="fb-button yellow"
|
||||
onClick={this.update}
|
||||
>
|
||||
{t("Update")}
|
||||
</button>
|
||||
<button
|
||||
className="fb-button green"
|
||||
onClick={this.run}
|
||||
>
|
||||
{t("Run")}
|
||||
</button>
|
||||
</Col>
|
||||
</fieldset>
|
||||
</Row>
|
||||
</MustBeOnline>
|
||||
</WidgetBody>
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,14 @@ export class FarmwarePage extends React.Component<FarmwareProps, {}> {
|
|||
<Photos
|
||||
dispatch={this.props.dispatch}
|
||||
images={this.props.images}
|
||||
currentImage={this.props.currentImage} />
|
||||
currentImage={this.props.currentImage}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={4}>
|
||||
<Col xs={12} sm={5}>
|
||||
<FarmwarePanel
|
||||
syncStatus={this.props.syncStatus}
|
||||
farmwares={this.props.farmwares} />
|
||||
farmwares={this.props.farmwares}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
|
@ -42,7 +44,8 @@ export class FarmwarePage extends React.Component<FarmwareProps, {}> {
|
|||
V_LO={envGet("CAMERA_CALIBRATION_V_LO", this.props.env)}
|
||||
H_HI={envGet("CAMERA_CALIBRATION_H_HI", this.props.env)}
|
||||
S_HI={envGet("CAMERA_CALIBRATION_S_HI", this.props.env)}
|
||||
V_HI={envGet("CAMERA_CALIBRATION_V_HI", this.props.env)} />
|
||||
V_HI={envGet("CAMERA_CALIBRATION_V_HI", this.props.env)}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={5} smOffset={1}>
|
||||
<WeedDetector {...this.props} />
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import * as React from "react";
|
||||
import * as _ from "lodash";
|
||||
import * as moment from "moment";
|
||||
import { t } from "i18next";
|
||||
import { success, error } from "farmbot-toastr";
|
||||
import { Widget, WidgetHeader, WidgetBody } from "../ui/index";
|
||||
|
@ -7,6 +9,28 @@ import { PhotosProps } from "./interfaces";
|
|||
import { devices } from "../device";
|
||||
import { ToolTips } from "../constants";
|
||||
import { selectImage } from "../images/actions";
|
||||
import { WidgetFooter } from "../ui/widget_footer";
|
||||
import { safeStringFetch } from "../util";
|
||||
|
||||
interface MetaInfoProps {
|
||||
/** Default conversion is `attr_name ==> Attr Name`.
|
||||
* Setting a label property will over ride it to a differrent value.
|
||||
*/
|
||||
label?: string;
|
||||
attr: string;
|
||||
obj: any; /** Really, it's OK here! See safeStringFetch */
|
||||
}
|
||||
|
||||
function MetaInfo({ obj, attr, label }: MetaInfoProps) {
|
||||
let top = label || _.startCase(attr.split("_").join());
|
||||
let bottom = safeStringFetch(obj, attr);
|
||||
return (
|
||||
<div>
|
||||
<label>{top}: </label>
|
||||
<span>{bottom || "unknown"}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export class Photos extends React.Component<PhotosProps, {}> {
|
||||
|
||||
|
@ -16,22 +40,50 @@ export class Photos extends React.Component<PhotosProps, {}> {
|
|||
devices.current.takePhoto().then(ok, no);
|
||||
}
|
||||
|
||||
metaDatas() {
|
||||
let i = this.props.currentImage;
|
||||
if (i) {
|
||||
let { meta } = i.body;
|
||||
return Object.keys(meta).sort().map(function (key, index) {
|
||||
return <MetaInfo key={index} attr={key} obj={meta} />;
|
||||
});
|
||||
} else {
|
||||
return <MetaInfo attr={t("image")} obj={{ image: t("No meta data.") }} />;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Widget className="photos-widget">
|
||||
<WidgetHeader helpText={ToolTips.PHOTOS} title={"Photos"}>
|
||||
<button
|
||||
className="fb-button gray"
|
||||
onClick={this.takePhoto}
|
||||
>
|
||||
{t("Take Photo")}
|
||||
</button>
|
||||
</WidgetHeader>
|
||||
<WidgetBody>
|
||||
<ImageFlipper
|
||||
onFlip={(id) => { this.props.dispatch(selectImage(id)) }}
|
||||
currentImage={this.props.currentImage}
|
||||
images={this.props.images} />
|
||||
</WidgetBody>
|
||||
</Widget>
|
||||
let image = this.props.currentImage;
|
||||
return (
|
||||
<Widget className="photos-widget">
|
||||
<WidgetHeader helpText={ToolTips.PHOTOS} title={"Photos"}>
|
||||
<button
|
||||
className="fb-button gray"
|
||||
onClick={this.takePhoto}
|
||||
>
|
||||
{t("Take Photo")}
|
||||
</button>
|
||||
</WidgetHeader>
|
||||
<WidgetBody>
|
||||
<ImageFlipper
|
||||
onFlip={id => { this.props.dispatch(selectImage(id)); }}
|
||||
currentImage={this.props.currentImage}
|
||||
images={this.props.images}
|
||||
/>
|
||||
</WidgetBody>
|
||||
<WidgetFooter>
|
||||
{/** Separated from <MetaInfo /> for stylistic purposes. */}
|
||||
{image ?
|
||||
<div>
|
||||
<label>{t("Created At")}</label>
|
||||
<span>
|
||||
{moment(image.body.created_at).format("MMMM Do, YYYY h:mma")}
|
||||
</span>
|
||||
</div>
|
||||
: ""}
|
||||
{this.metaDatas()}
|
||||
</WidgetFooter>
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { ImageFlipperProps, ImageFlipperState } from "./interfaces";
|
||||
import * as React from "react";
|
||||
import { safeStringFetch } from "../util";
|
||||
import { t } from "i18next";
|
||||
import * as moment from "moment";
|
||||
import * as _ from "lodash";
|
||||
import * as moment from "moment";
|
||||
import { ImageFlipperProps, ImageFlipperState } from "./interfaces";
|
||||
import { safeStringFetch } from "../util";
|
||||
|
||||
export const PLACEHOLDER_FARMBOT = "/placeholder_farmbot.jpg";
|
||||
|
||||
export class ImageFlipper
|
||||
extends React.Component<ImageFlipperProps, Partial<ImageFlipperState>> {
|
||||
export class ImageFlipper extends
|
||||
React.Component<ImageFlipperProps, Partial<ImageFlipperState>> {
|
||||
|
||||
state: ImageFlipperState = { isLoaded: false };
|
||||
|
||||
|
@ -24,12 +24,14 @@ export class ImageFlipper
|
|||
<p>{t(`Image loading (try refreshing)`)}</p>
|
||||
<img
|
||||
className="image-flipper-image"
|
||||
src={PLACEHOLDER_FARMBOT} />
|
||||
src={PLACEHOLDER_FARMBOT}
|
||||
/>
|
||||
</div>)}
|
||||
<img
|
||||
onLoad={() => this.setState({ isLoaded: true })}
|
||||
className={`image-flipper-image is-loaded-${this.state.isLoaded}`}
|
||||
src={url} />
|
||||
src={url}
|
||||
/>
|
||||
</div>;
|
||||
} else {
|
||||
return <div className="no-flipper-image-container">
|
||||
|
@ -37,23 +39,12 @@ export class ImageFlipper
|
|||
Once you do, they will show up here.`)}</p>
|
||||
<img
|
||||
className="image-flipper-image"
|
||||
src={PLACEHOLDER_FARMBOT} />
|
||||
src={PLACEHOLDER_FARMBOT}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
metaDatas() {
|
||||
let i = this.props.currentImage;
|
||||
if (i) {
|
||||
let { meta } = i.body;
|
||||
return Object.keys(meta).sort().map(function (key, index) {
|
||||
return <MetaInfo key={index} attr={key} obj={meta} />;
|
||||
});
|
||||
} else {
|
||||
return <MetaInfo attr={"image"} obj={{ image: "No meta data." }} />;
|
||||
}
|
||||
}
|
||||
|
||||
go = (increment: -1 | 1) => () => {
|
||||
let { images, currentImage } = this.props;
|
||||
let uuids = images.map(x => x.uuid);
|
||||
|
@ -66,59 +57,22 @@ export class ImageFlipper
|
|||
|
||||
render() {
|
||||
let image = this.imageJSX();
|
||||
let i = this.props.currentImage;
|
||||
return <div>
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<div className="image-flipper">
|
||||
{image}
|
||||
<button
|
||||
onClick={this.go(1)}
|
||||
className="image-flipper-left fb-button"
|
||||
>
|
||||
{t("Prev")}
|
||||
</button>
|
||||
<button
|
||||
onClick={this.go(-1)}
|
||||
className="image-flipper-right fb-button"
|
||||
>
|
||||
{t("Next")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className="image-flipper">
|
||||
{image}
|
||||
<button
|
||||
onClick={this.go(1)}
|
||||
className="image-flipper-left fb-button"
|
||||
>
|
||||
{t("Prev")}
|
||||
</button>
|
||||
<button
|
||||
onClick={this.go(-1)}
|
||||
className="image-flipper-right fb-button"
|
||||
>
|
||||
{t("Next")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="weed-detector-meta">
|
||||
{/** Separated from <MetaInfo /> for stylistic purposes. */}
|
||||
{i ?
|
||||
<div className="created-at">
|
||||
<label>{t("Created At")}</label>
|
||||
<span>
|
||||
{moment(i.body.created_at).format("MMMM Do, YYYY h:mma")}
|
||||
</span>
|
||||
</div>
|
||||
: ""}
|
||||
<div className="meta-coordinates">
|
||||
{this.metaDatas()}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface MetaInfoProps {
|
||||
/** Default conversion is `attr_name ==> Attr Name`.
|
||||
* Setting a label property will over ride it to a differrent value.
|
||||
*/
|
||||
label?: string;
|
||||
attr: string;
|
||||
obj: any; /** Really, it's OK here! See safeStringFetch */
|
||||
}
|
||||
|
||||
function MetaInfo({ obj, attr, label }: MetaInfoProps) {
|
||||
let top = label || _.startCase(attr.split("_").join());
|
||||
let bottom = safeStringFetch(obj, attr);
|
||||
return <div className="coordinate">
|
||||
<label>{top}</label>
|
||||
<span>{bottom || "unknown"}</span>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -4,11 +4,10 @@ import { JSXChildren } from "../util";
|
|||
|
||||
interface WidgetFooterProps {
|
||||
children?: JSXChildren;
|
||||
helpText?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export function WidgetFooter(props: WidgetFooterProps) {
|
||||
return <div className="widget-footer">
|
||||
{props.children}
|
||||
</div>;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue