message fixes and updates

pull/1162/head
gabrielburnworth 2019-04-18 15:58:09 -07:00
parent e032acddfd
commit c690bf760b
9 changed files with 197 additions and 53 deletions

View File

@ -15,6 +15,12 @@ module Devices
device = Device.create!({name: "Farmbot"}.merge(inputs.except(:user)))
Enigmas::Create.run!(device: device,
problem_tag: Enigma::SEED_DATA)
Enigmas::Create.run!(device: device,
problem_tag: Enigma::TOUR)
Enigmas::Create.run!(device: device,
problem_tag: Enigma::USER)
Enigmas::Create.run!(device: device,
problem_tag: Enigma::DOCUMENTATION)
ActiveRecord::Base.transaction do
# TODO: This is a really, really, really old

View File

@ -316,12 +316,13 @@ export namespace ToolTips {
// Tools
export const TOOL_LIST =
trim(`This is a list of all your FarmBot tools and seed containers. Click the Edit button
to add, edit, or delete tools or seed containers.`);
trim(`This is a list of all your FarmBot tools and seed containers.
Click the Edit button to add, edit, or delete tools or seed containers.`);
export const TOOLBAY_LIST =
trim(`Tool slots are where you store your FarmBot tools and seed containers, which should be
reflective of your real FarmBot hardware configuration.`);
trim(`Tool slots are where you store your FarmBot tools and seed
containers, which should be reflective of your real FarmBot hardware
configuration.`);
// Logs
export const LOGS =
@ -420,6 +421,11 @@ export namespace Content {
trim(`When you're finished with a message, press the x button in the
top right of the card to dismiss it.`);
export const FIRMWARE_MISSING =
trim(`Please choose a firmware version to install. Your choice should be
based on the type of electronics in your FarmBot according to the reference
table below.`);
// App Settings
export const CONFIRM_STEP_DELETION =
trim(`Show a confirmation dialog when deleting a sequence step.`);

View File

@ -939,7 +939,7 @@ ul {
}
.messages-page {
max-width: 600px;
max-width: 785px;
padding-left: 2rem !important;
padding-right: 2rem !important;
.link-to-logs {
@ -1104,7 +1104,7 @@ ul {
.tour-list {
margin: auto;
width: 75%;
max-width: 300px;
margin-top: 1rem;
}
@ -1171,9 +1171,23 @@ ul {
box-shadow: 0px 2px 5px $medium_gray;
background: $off_white;
.problem-alert-title {
position: relative;
margin-bottom: 1rem;
line-height: 1rem;
.fa-exclamation-triangle,
.fa-check-square,
.fa-info-circle {
font-size: 1.6rem;
padding-right: 0.5rem;
}
.fa-exclamation-triangle {
color: $orange;
padding-right: 0.5rem;
}
.fa-check-square {
color: $green;
}
.fa-info-circle {
color: $blue;
}
h3 {
color: $dark_gray;
@ -1182,13 +1196,16 @@ ul {
}
p {
display: inline;
padding: 1rem;
color: $medium_gray;
font-size: 1.2rem;
margin-left: 2rem;
white-space: pre;
}
.fa-times {
position: absolute;
top: 0;
right: 0;
color: $medium_light_gray;
float: right;
&:hover {
color: $dark_gray;
}
@ -1215,5 +1232,32 @@ ul {
float: none;
margin-bottom: 2rem;
}
.fb-button {
margin-top: 0.5rem;
}
}
}
.firmware-alerts {
max-width: 600px;
}
.firmware-hardware-choice-table {
margin: 2rem;
margin-top: 1rem;
width: 93%;
border: 1px solid $gray;
font-size: 1.2rem;
th {
background: $light_gray;
font-weight: normal;
}
td {
background: $off_white;
color: $medium_gray;
}
code {
background: $light_gray;
color: $dark_gray;
}
}

View File

@ -39,6 +39,21 @@ export interface FirmwareHardwareStatusDetailsProps {
dispatch: Function;
}
export interface FlashFirmwareBtnProps {
apiFirmwareValue: string | undefined;
botOnline: boolean;
}
export const FlashFirmwareBtn = (props: FlashFirmwareBtnProps) => {
const { apiFirmwareValue } = props;
return <button className="fb-button yellow"
disabled={!apiFirmwareValue || !props.botOnline}
onClick={() => isFwHardwareValue(apiFirmwareValue) &&
flashFirmware(apiFirmwareValue)}>
{t("flash firmware")}
</button>;
};
export interface FirmwareActionsProps {
apiFirmwareValue: string | undefined;
botOnline: boolean;
@ -51,12 +66,7 @@ export const FirmwareActions = (props: FirmwareActionsProps) => {
{trim(`${t("Flash the")} ${lookup(apiFirmwareValue) || ""}
${t("firmware to your device")}:`)}
</p>
<button className="fb-button yellow"
disabled={!apiFirmwareValue || !props.botOnline}
onClick={() => isFwHardwareValue(apiFirmwareValue) &&
flashFirmware(apiFirmwareValue)}>
{t("flash firmware")}
</button>
<FlashFirmwareBtn {...props} />
</div>;
};
@ -98,7 +108,7 @@ export const FirmwareHardwareStatus = (props: FirmwareHardwareStatusProps) => {
const { firmware_hardware } = props.bot.hardware.configuration;
const status = props.apiFirmwareValue == firmware_hardware &&
props.apiFirmwareValue == boardType(firmware_version);
return <Popover position={Position.BOTTOM}>
return <Popover position={Position.TOP}>
<FirmwareHardwareStatusIcon
firmwareHardware={firmware_hardware}
status={status} />

View File

@ -53,7 +53,7 @@ describe("<Alerts />", () => {
p.alerts = [FIRMWARE_MISSING_ALERT, SEED_DATA_MISSING_ALERT];
const wrapper = mount(<Alerts {...p} />);
expect(wrapper.text()).toContain("2");
expect(wrapper.text()).toContain("Your device has no firmware installed");
expect(wrapper.text()).toContain("Your device has no firmware");
expect(wrapper.text()).toContain("Choose your FarmBot");
});
@ -89,7 +89,7 @@ describe("<FirmwareAlerts />", () => {
};
const wrapper = mount(<FirmwareAlerts {...p} />);
expect(wrapper.text()).toContain("1");
expect(wrapper.text()).toContain("Your device has no firmware installed");
expect(wrapper.text()).toContain("Your device has no firmware");
});
});

View File

@ -1,12 +1,15 @@
jest.mock("../../devices/actions", () => ({ updateConfig: jest.fn() }));
jest.mock("../../api/crud", () => ({ destroy: jest.fn() }));
import * as React from "react";
import { mount } from "enzyme";
import { AlertCard } from "../cards";
import { AlertCard, changeFirmwareHardware } from "../cards";
import { AlertCardProps } from "../interfaces";
import { fakeTimeSettings } from "../../__test_support__/fake_time_settings";
import { FBSelect } from "../../ui";
import { destroy } from "../../api/crud";
import { updateConfig } from "../../devices/actions";
describe("<AlertCard />", () => {
const fakeProps = (): AlertCardProps => ({
@ -35,9 +38,8 @@ describe("<AlertCard />", () => {
const p = fakeProps();
p.alert.problem_tag = "farmbot_os.firmware.missing";
const wrapper = mount(<AlertCard {...p} />);
expect(wrapper.text()).toContain("Firmware missing");
wrapper.find(".fa-times").simulate("click");
expect(destroy).not.toHaveBeenCalled();
expect(wrapper.text()).toContain("Your device has no firmware");
expect(wrapper.find(".fa-times").length).toEqual(0);
});
it("renders seed data card", () => {
@ -69,3 +71,10 @@ describe("<AlertCard />", () => {
expect(wrapper.text()).toContain("Learn");
});
});
describe("changeFirmwareHardware()", () => {
it("changes firmware hardware value", () => {
changeFirmwareHardware(jest.fn())({ label: "Arduino", value: "arduino" });
expect(updateConfig).toHaveBeenCalledWith({ firmware_hardware: "arduino" });
});
});

View File

@ -17,27 +17,29 @@ export const sortAlerts = (alerts: Alert[]): Alert[] =>
export const FirmwareAlerts = (props: FirmwareAlertsProps) => {
const alerts = betterCompact(Object.values(props.bot.hardware.enigmas || {}));
const firmwareAlerts = sortAlerts(alerts)
.filter(x => x.problem_tag && x.priority && x.created_at)
.filter(x => splitProblemTag(x.problem_tag).noun === "firmware");
return <div className="firmware-alerts">
{firmwareAlerts.filter(x => x.problem_tag && x.priority && x.created_at)
.map((x, i) =>
<AlertCard key={i}
alert={x}
dispatch={props.dispatch}
apiFirmwareValue={props.apiFirmwareValue}
timeSettings={props.timeSettings} />)}
{firmwareAlerts.map((x, i) =>
<AlertCard key={i}
alert={x}
dispatch={props.dispatch}
apiFirmwareValue={props.apiFirmwareValue}
timeSettings={props.timeSettings} />)}
</div>;
};
export const Alerts = (props: AlertsProps) =>
<div className="problem-alerts">
<div className="problem-alerts-content">
{sortAlerts(props.alerts).map((x, i) =>
<AlertCard key={i}
alert={x}
dispatch={props.dispatch}
apiFirmwareValue={props.apiFirmwareValue}
timeSettings={props.timeSettings}
findApiAlertById={props.findApiAlertById} />)}
{sortAlerts(props.alerts)
.filter(x => x.problem_tag && x.priority && x.created_at)
.map((x, i) =>
<AlertCard key={i}
alert={x}
dispatch={props.dispatch}
apiFirmwareValue={props.apiFirmwareValue}
timeSettings={props.timeSettings}
findApiAlertById={props.findApiAlertById} />)}
</div>
</div>;

View File

@ -8,13 +8,17 @@ import {
} from "./interfaces";
import { formatLogTime } from "../logs";
import {
FirmwareActions
FlashFirmwareBtn
} from "../devices/components/fbos_settings/firmware_hardware_status";
import { DropDownItem, Row, Col, FBSelect, docLink } from "../ui";
import { Content } from "../constants";
import { TourList } from "../help/tour_list";
import { splitProblemTag } from "./alerts";
import { destroy } from "../api/crud";
import {
isFwHardwareValue
} from "../devices/components/fbos_settings/board_type";
import { updateConfig } from "../devices/actions";
export const AlertCard = (props: AlertCardProps) => {
const { alert, timeSettings, findApiAlertById, dispatch } = props;
@ -34,23 +38,22 @@ export const AlertCard = (props: AlertCardProps) => {
case "api.documentation.unread":
return <DocumentationUnread {...commonProps} />;
default:
return UnknownAlert(commonProps);
return <UnknownAlert {...commonProps} />;
}
};
const dismissAlert = (props: DismissAlertProps) => () =>
(props.id && props.findApiAlertById && props.dispatch)
? props.dispatch(destroy(props.findApiAlertById(props.id)))
: () => { };
(props.id && props.findApiAlertById && props.dispatch) &&
props.dispatch(destroy(props.findApiAlertById(props.id)));
const AlertCardTemplate = (props: AlertCardTemplateProps) => {
const { alert, findApiAlertById, dispatch } = props;
return <div className={`problem-alert ${props.className}`}>
<div className="problem-alert-title">
<i className="fa fa-exclamation-triangle" />
<i className={`fa fa-${props.iconName || "exclamation-triangle"}`} />
<h3>{t(props.title)}</h3>
<p>{formatLogTime(alert.created_at, props.timeSettings)}</p>
<i className="fa fa-times"
onClick={dismissAlert({ id: alert.id, findApiAlertById, dispatch })} />
{alert.id && <i className="fa fa-times"
onClick={dismissAlert({ id: alert.id, findApiAlertById, dispatch })} />}
</div>
<div className="problem-alert-content">
<p>{t(props.message)}</p>
@ -74,18 +77,77 @@ const UnknownAlert = (props: CommonAlertCardProps) => {
findApiAlertById={props.findApiAlertById} />;
};
const FIRMWARE_CHOICES: DropDownItem[] = [
{ label: "Arduino/RAMPS (Genesis v1.2)", value: "arduino" },
{ label: "Farmduino (Genesis v1.3)", value: "farmduino" },
{ label: "Farmduino (Genesis v1.4)", value: "farmduino_k14" },
];
const FIRMWARE_CHOICES_DDI: { [x: string]: DropDownItem } = {};
FIRMWARE_CHOICES.map(x => FIRMWARE_CHOICES_DDI[x.value] = x);
const FirmwareChoiceTable = () =>
<table className="firmware-hardware-choice-table">
<thead>
<tr>
<th>{t("FarmBot Version")}</th>
<th>{t("Electronics Board")}</th>
<th>{t("Firmware Name")}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{"Genesis v1.2"}</td>
<td>{"RAMPS"}</td>
<td><code>{FIRMWARE_CHOICES_DDI["arduino"].label}</code></td>
</tr>
<tr>
<td>{"Genesis v1.3"}</td>
<td>{"Farmduino"}</td>
<td><code>{FIRMWARE_CHOICES_DDI["farmduino"].label}</code></td>
</tr>
<tr>
<td>{"Genesis v1.4"}</td>
<td>{"Farmduino"}</td>
<td><code>{FIRMWARE_CHOICES_DDI["farmduino_k14"].label}</code></td>
</tr>
</tbody>
</table>;
export const changeFirmwareHardware = (dispatch: Function | undefined) =>
(ddi: DropDownItem) => {
if (isFwHardwareValue(ddi.value)) {
dispatch && dispatch(updateConfig({ firmware_hardware: ddi.value }));
}
};
const FirmwareMissing = (props: FirmwareMissingProps) =>
<AlertCardTemplate
alert={props.alert}
className={"firmware-missing-alert"}
title={t("Firmware missing")}
message={t("Your device has no firmware installed.")}
title={t("Your device has no firmware")}
message={t(Content.FIRMWARE_MISSING)}
timeSettings={props.timeSettings}
dispatch={props.dispatch}
findApiAlertById={props.findApiAlertById}>
<FirmwareActions
apiFirmwareValue={props.apiFirmwareValue}
botOnline={true} />
<Row>
<FirmwareChoiceTable />
<Col xs={4}>
<label>{t("Choose Firmware")}</label>
</Col>
<Col xs={5}>
<FBSelect
key={props.apiFirmwareValue}
list={FIRMWARE_CHOICES}
selectedItem={FIRMWARE_CHOICES_DDI[props.apiFirmwareValue || "arduino"]}
onChange={changeFirmwareHardware(props.dispatch)} />
</Col>
<Col xs={3}>
<FlashFirmwareBtn
apiFirmwareValue={props.apiFirmwareValue}
botOnline={true} />
</Col>
</Row>
</AlertCardTemplate>;
const SEED_DATA_OPTIONS: DropDownItem[] = [
@ -107,7 +169,8 @@ class SeedDataMissing
message={t(Content.SEED_DATA_SELECTION)}
timeSettings={this.props.timeSettings}
dispatch={this.props.dispatch}
findApiAlertById={this.props.findApiAlertById}>
findApiAlertById={this.props.findApiAlertById}
iconName={"check-square"}>
<Row>
<Col xs={4}>
<label>{t("Choose your FarmBot")}</label>
@ -132,7 +195,8 @@ const TourNotTaken = (props: TourNotTakenProps) =>
message={t(Content.TAKE_A_TOUR)}
timeSettings={props.timeSettings}
dispatch={props.dispatch}
findApiAlertById={props.findApiAlertById}>
findApiAlertById={props.findApiAlertById}
iconName={"info-circle"}>
<p>{t("Choose a tour to begin")}:</p>
<TourList dispatch={props.dispatch} />
</AlertCardTemplate>;
@ -145,7 +209,8 @@ const UserNotWelcomed = (props: CommonAlertCardProps) =>
message={t(Content.WELCOME)}
timeSettings={props.timeSettings}
dispatch={props.dispatch}
findApiAlertById={props.findApiAlertById}>
findApiAlertById={props.findApiAlertById}
iconName={"info-circle"}>
<p>
{t("You're currently viewing the")} <b>{t("Message Center")}</b>.
&nbsp;{t(Content.MESSAGE_CENTER_WELCOME)}
@ -163,7 +228,8 @@ const DocumentationUnread = (props: CommonAlertCardProps) =>
message={t(Content.READ_THE_DOCS)}
timeSettings={props.timeSettings}
dispatch={props.dispatch}
findApiAlertById={props.findApiAlertById}>
findApiAlertById={props.findApiAlertById}
iconName={"info-circle"}>
<p>
{t("Head over to")}
&nbsp;<a href={docLink()} target="_blank"

View File

@ -58,6 +58,7 @@ export interface AlertCardTemplateProps {
children?: React.ReactNode;
findApiAlertById?(id: number): UUID;
dispatch?: Function;
iconName?: string;
}
export interface DismissAlertProps {