Merge branch 'staging' of https://github.com/FarmBot/Farmbot-Web-App into plant_stages
commit
b259970e98
|
@ -63,7 +63,7 @@ FarmBot::Application.routes.draw do
|
|||
get "/tos_update" => "dashboard#tos_update", as: :tos_update
|
||||
post "/csp_reports" => "dashboard#csp_reports", as: :csp_report
|
||||
|
||||
get "/password_reset/:token" => "dashboard#password_reset", as: :password_reset
|
||||
get "/password_reset/*token" => "dashboard#password_reset", as: :password_reset
|
||||
get "/verify/:token" => "dashboard#verify", as: :verify_user
|
||||
|
||||
match "/app/*path", to: "dashboard#main_app", via: :all, constraints: { format: "html" }
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
"webpack-uglify-js-plugin": "^1.1.9",
|
||||
"weinre": "^2.0.0-pre-I0Z7U9OV",
|
||||
"which": "^1.3.0",
|
||||
"yarn": "^1.2.1"
|
||||
"yarn": "^1.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jscpd": "^0.6.15",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,44 @@
|
|||
jest.mock("fastclick", () => ({ attach: jest.fn() }));
|
||||
|
||||
import {
|
||||
topLevelRoutes,
|
||||
designerRoutes,
|
||||
maybeReplaceDesignerModules
|
||||
} from "../route_config";
|
||||
import { RouterState, RedirectFunction } from "react-router";
|
||||
|
||||
async function makeSureTheyAreRoutes(input: typeof topLevelRoutes.childRoutes) {
|
||||
const cb = jest.fn();
|
||||
await Promise.all(input.map(route => route.getComponent(undefined, cb)));
|
||||
expect(cb).toHaveBeenCalled();
|
||||
expect(cb).toHaveBeenCalledTimes(input.length);
|
||||
cb.mock.calls.map(x => expect(!!x[1]).toBeTruthy());
|
||||
}
|
||||
|
||||
describe("top level routes", () => {
|
||||
it("generates all of them",
|
||||
() => makeSureTheyAreRoutes(topLevelRoutes.childRoutes));
|
||||
});
|
||||
|
||||
describe("designer routes", () => {
|
||||
it("generates all of them",
|
||||
() => makeSureTheyAreRoutes(designerRoutes.childRoutes));
|
||||
});
|
||||
|
||||
describe("maybeReplaceDesignerModules", () => {
|
||||
it("does replace the route", () => {
|
||||
const pathname = "/app/designer";
|
||||
const next = { location: { pathname } } as RouterState;
|
||||
const replace = jest.fn() as RedirectFunction;
|
||||
maybeReplaceDesignerModules(next, replace);
|
||||
expect(replace).toHaveBeenCalledWith(`${pathname}/plants`);
|
||||
});
|
||||
|
||||
it("does not replace the route", () => {
|
||||
const pathname = "/app/nope";
|
||||
const next = { location: { pathname } } as RouterState;
|
||||
const replace = jest.fn() as RedirectFunction;
|
||||
maybeReplaceDesignerModules(next, replace);
|
||||
expect(replace).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -10,7 +10,7 @@ import {
|
|||
import { SpecialStatus } from "../../resources/tagged_resources";
|
||||
import Axios from "axios";
|
||||
import { API } from "../../api/index";
|
||||
import { prettyPrintApiErrors } from "../../util";
|
||||
import { prettyPrintApiErrors, equals } from "../../util";
|
||||
import { success, error } from "farmbot-toastr/dist";
|
||||
|
||||
interface PasswordForm {
|
||||
|
@ -19,26 +19,18 @@ interface PasswordForm {
|
|||
password: string;
|
||||
}
|
||||
|
||||
interface ChangePWState {
|
||||
status: SpecialStatus;
|
||||
form: PasswordForm
|
||||
}
|
||||
const EMPTY_FORM = {
|
||||
new_password: "",
|
||||
new_password_confirmation: "",
|
||||
password: ""
|
||||
};
|
||||
interface ChangePWState { status: SpecialStatus; form: PasswordForm }
|
||||
|
||||
const EMPTY_FORM =
|
||||
({ new_password: "", new_password_confirmation: "", password: "" });
|
||||
|
||||
export class ChangePassword extends React.Component<{}, ChangePWState> {
|
||||
state: ChangePWState = {
|
||||
status: SpecialStatus.SAVED,
|
||||
form: EMPTY_FORM
|
||||
};
|
||||
state: ChangePWState = { status: SpecialStatus.SAVED, form: EMPTY_FORM };
|
||||
|
||||
/** Set the `status` flag to `undefined`, but only if the form is empty.
|
||||
* Useful when the user manually clears the form. */
|
||||
maybeClearForm = () => wowFixMe(EMPTY_FORM, this.state.form) ?
|
||||
this.clearForm() : false;
|
||||
maybeClearForm =
|
||||
() => equals(EMPTY_FORM, this.state.form) ? this.clearForm() : false;
|
||||
|
||||
clearForm = () => this.setState({ status: SpecialStatus.SAVED, form: EMPTY_FORM });
|
||||
|
||||
|
@ -103,9 +95,3 @@ export class ChangePassword extends React.Component<{}, ChangePWState> {
|
|||
</Widget>;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Why does Object.is() not work when comparing EMPTY_FORM to
|
||||
// this.state.form? Using this in the meantime. PRs and feedback welcome.
|
||||
function wowFixMe<T>(l: T, r: T): boolean {
|
||||
return (JSON.stringify(l) === JSON.stringify(r));
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ const MUST_LOAD: ResourceName[] = [
|
|||
export class App extends React.Component<AppProps, {}> {
|
||||
componentDidCatch(x: Error, y: React.ErrorInfo) { catchErrors(x, y); }
|
||||
|
||||
get isLoaded() {
|
||||
private get isLoaded() {
|
||||
return (MUST_LOAD.length ===
|
||||
_.intersection(this.props.loaded, MUST_LOAD).length);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { validBotLocationData } from "../util";
|
||||
import { JogMovementControlsProps } from "./interfaces";
|
||||
|
||||
const _ = (nr_steps: number | undefined, steps_mm: number | undefined) => {
|
||||
return (nr_steps || 0) / (steps_mm || 1);
|
||||
};
|
||||
|
||||
function calculateAxialLengths(props: JogMovementControlsProps) {
|
||||
const mp = props.bot.hardware.mcu_params;
|
||||
|
||||
return {
|
||||
x: _(mp.movement_axis_nr_steps_x, mp.movement_step_per_mm_x),
|
||||
y: _(mp.movement_axis_nr_steps_y, mp.movement_step_per_mm_y),
|
||||
z: _(mp.movement_axis_nr_steps_z, mp.movement_step_per_mm_z),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildDirectionProps(props: JogMovementControlsProps) {
|
||||
const { location_data, mcu_params } = props.bot.hardware;
|
||||
const botLocationData = validBotLocationData(location_data);
|
||||
const lengths = calculateAxialLengths(props);
|
||||
return {
|
||||
x: {
|
||||
isInverted: props.x_axis_inverted,
|
||||
stopAtHome: !!mcu_params.movement_stop_at_home_x,
|
||||
stopAtMax: !!mcu_params.movement_stop_at_max_x,
|
||||
axisLength: lengths.x,
|
||||
negativeOnly: !!mcu_params.movement_home_up_x,
|
||||
position: botLocationData.position.x
|
||||
},
|
||||
y: {
|
||||
isInverted: props.y_axis_inverted,
|
||||
stopAtHome: !!mcu_params.movement_stop_at_home_y,
|
||||
stopAtMax: !!mcu_params.movement_stop_at_max_y,
|
||||
axisLength: lengths.y,
|
||||
negativeOnly: !!mcu_params.movement_home_up_y,
|
||||
position: botLocationData.position.y
|
||||
},
|
||||
z: {
|
||||
isInverted: props.z_axis_inverted,
|
||||
stopAtHome: !!mcu_params.movement_stop_at_home_z,
|
||||
stopAtMax: !!mcu_params.movement_stop_at_max_z,
|
||||
axisLength: lengths.z,
|
||||
negativeOnly: !!mcu_params.movement_home_up_z,
|
||||
position: botLocationData.position.z
|
||||
},
|
||||
};
|
||||
}
|
|
@ -3,116 +3,84 @@ import { DirectionButton } from "./direction_button";
|
|||
import { homeAll } from "../devices/actions";
|
||||
import { JogMovementControlsProps } from "./interfaces";
|
||||
import { getDevice } from "../device";
|
||||
import { validBotLocationData } from "../util";
|
||||
import { buildDirectionProps } from "./direction_axes_props";
|
||||
|
||||
export class JogButtons extends React.Component<JogMovementControlsProps, {}> {
|
||||
render() {
|
||||
const { location_data, mcu_params } = this.props.bot.hardware;
|
||||
const botLocationData = validBotLocationData(location_data);
|
||||
const directionAxesProps = {
|
||||
x: {
|
||||
isInverted: this.props.x_axis_inverted,
|
||||
stopAtHome: !!mcu_params.movement_stop_at_home_x,
|
||||
stopAtMax: !!mcu_params.movement_stop_at_max_x,
|
||||
axisLength: (mcu_params.movement_axis_nr_steps_x || 0)
|
||||
/ (mcu_params.movement_step_per_mm_x || 1),
|
||||
negativeOnly: !!mcu_params.movement_home_up_x,
|
||||
position: botLocationData.position.x
|
||||
},
|
||||
y: {
|
||||
isInverted: this.props.y_axis_inverted,
|
||||
stopAtHome: !!mcu_params.movement_stop_at_home_y,
|
||||
stopAtMax: !!mcu_params.movement_stop_at_max_y,
|
||||
axisLength: (mcu_params.movement_axis_nr_steps_y || 0)
|
||||
/ (mcu_params.movement_step_per_mm_y || 1),
|
||||
negativeOnly: !!mcu_params.movement_home_up_y,
|
||||
position: botLocationData.position.y
|
||||
},
|
||||
z: {
|
||||
isInverted: this.props.z_axis_inverted,
|
||||
stopAtHome: !!mcu_params.movement_stop_at_home_z,
|
||||
stopAtMax: !!mcu_params.movement_stop_at_max_z,
|
||||
axisLength: (mcu_params.movement_axis_nr_steps_z || 0)
|
||||
/ (mcu_params.movement_step_per_mm_z || 1),
|
||||
negativeOnly: !!mcu_params.movement_home_up_z,
|
||||
position: botLocationData.position.z
|
||||
},
|
||||
};
|
||||
return <table className="jog-table" style={{ border: 0 }}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<button
|
||||
className="i fa fa-camera arrow-button fb-button"
|
||||
onClick={() => getDevice().takePhoto()} />
|
||||
</td>
|
||||
<td />
|
||||
<td />
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="y"
|
||||
direction="up"
|
||||
directionAxisProps={directionAxesProps.y}
|
||||
steps={this.props.bot.stepSize || 1000}
|
||||
disabled={this.props.disabled} />
|
||||
</td>
|
||||
<td />
|
||||
<td />
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="z"
|
||||
direction="up"
|
||||
directionAxisProps={directionAxesProps.z}
|
||||
steps={this.props.bot.stepSize || 1000}
|
||||
disabled={this.props.disabled} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button
|
||||
className="i fa fa-home arrow-button fb-button"
|
||||
onClick={() => homeAll(100)}
|
||||
disabled={this.props.disabled || false} />
|
||||
</td>
|
||||
<td />
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="x"
|
||||
direction="left"
|
||||
directionAxisProps={directionAxesProps.x}
|
||||
steps={this.props.bot.stepSize || 1000}
|
||||
disabled={this.props.disabled} />
|
||||
</td>
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="y"
|
||||
direction="down"
|
||||
directionAxisProps={directionAxesProps.y}
|
||||
steps={this.props.bot.stepSize || 1000}
|
||||
disabled={this.props.disabled} />
|
||||
</td>
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="x"
|
||||
direction="right"
|
||||
directionAxisProps={directionAxesProps.x}
|
||||
steps={this.props.bot.stepSize || 1000}
|
||||
disabled={this.props.disabled} />
|
||||
</td>
|
||||
<td />
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="z"
|
||||
direction="down"
|
||||
directionAxisProps={directionAxesProps.z}
|
||||
steps={this.props.bot.stepSize || 1000}
|
||||
disabled={this.props.disabled} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>;
|
||||
}
|
||||
export function JogButtons(props: JogMovementControlsProps) {
|
||||
const directionAxesProps = buildDirectionProps(props);
|
||||
return <table className="jog-table" style={{ border: 0 }}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<button
|
||||
className="i fa fa-camera arrow-button fb-button"
|
||||
onClick={() => getDevice().takePhoto()} />
|
||||
</td>
|
||||
<td />
|
||||
<td />
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="y"
|
||||
direction="up"
|
||||
directionAxisProps={directionAxesProps.y}
|
||||
steps={props.bot.stepSize || 1000}
|
||||
disabled={props.disabled} />
|
||||
</td>
|
||||
<td />
|
||||
<td />
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="z"
|
||||
direction="up"
|
||||
directionAxisProps={directionAxesProps.z}
|
||||
steps={props.bot.stepSize || 1000}
|
||||
disabled={props.disabled} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button
|
||||
className="i fa fa-home arrow-button fb-button"
|
||||
onClick={() => homeAll(100)}
|
||||
disabled={props.disabled || false} />
|
||||
</td>
|
||||
<td />
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="x"
|
||||
direction="left"
|
||||
directionAxisProps={directionAxesProps.x}
|
||||
steps={props.bot.stepSize || 1000}
|
||||
disabled={props.disabled} />
|
||||
</td>
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="y"
|
||||
direction="down"
|
||||
directionAxisProps={directionAxesProps.y}
|
||||
steps={props.bot.stepSize || 1000}
|
||||
disabled={props.disabled} />
|
||||
</td>
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="x"
|
||||
direction="right"
|
||||
directionAxisProps={directionAxesProps.x}
|
||||
steps={props.bot.stepSize || 1000}
|
||||
disabled={props.disabled} />
|
||||
</td>
|
||||
<td />
|
||||
<td>
|
||||
<DirectionButton
|
||||
axis="z"
|
||||
direction="down"
|
||||
directionAxisProps={directionAxesProps.z}
|
||||
steps={props.bot.stepSize || 1000}
|
||||
disabled={props.disabled} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>;
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ export class ToggleButton extends React.Component<ToggleButtonProps, {}> {
|
|||
disabled={!!this.props.disabled}
|
||||
className={this.css() + addCss}
|
||||
onClick={cb}>
|
||||
{this.caption()}
|
||||
{t(this.caption())}
|
||||
</button>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,12 @@ import { Xyz, BotPosition } from "./devices/interfaces";
|
|||
import { McuParams } from "farmbot";
|
||||
import { getDevice } from "./device";
|
||||
|
||||
export interface State {
|
||||
interface State {
|
||||
isOpen: boolean;
|
||||
stepSize: number;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
interface Props {
|
||||
dispatch: Function;
|
||||
axisInversion: Record<Xyz, boolean>;
|
||||
botPosition: BotPosition;
|
||||
|
@ -24,7 +24,7 @@ export class ControlsPopup extends React.Component<Props, Partial<State>> {
|
|||
stepSize: 100
|
||||
};
|
||||
|
||||
toggle = (property: keyof State) => () =>
|
||||
private toggle = (property: keyof State) => () =>
|
||||
this.setState({ [property]: !this.state[property] });
|
||||
|
||||
public render() {
|
||||
|
|
|
@ -46,8 +46,7 @@ export class LastSeen extends React.Component<LastSeenProps, {}> {
|
|||
};
|
||||
return t(text, data);
|
||||
} else {
|
||||
return t(" The device has never been seen. Most likely, " +
|
||||
"there is a network connectivity issue on the device's end.");
|
||||
return t("The device has never been seen. Most likely, there is a network connectivity issue on the device's end.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,8 +31,8 @@ export class HardwareSettings extends
|
|||
<SaveBtn
|
||||
status={bot.isUpdating ? SpecialStatus.SAVING : SpecialStatus.SAVED}
|
||||
dirtyText={" "}
|
||||
savingText={"Updating..."}
|
||||
savedText={"saved"}
|
||||
savingText={t("Updating...")}
|
||||
savedText={t("saved")}
|
||||
hidden={false} />
|
||||
</MustBeOnline>
|
||||
</WidgetHeader>
|
||||
|
|
|
@ -8,9 +8,11 @@ jest.mock("../../../../device", () => ({
|
|||
import * as React from "react";
|
||||
import { MotorsProps } from "../../interfaces";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
import { Motors, StepsPerMmSettings } from "../motors";
|
||||
import { Motors } from "../motors";
|
||||
import { render, shallow, mount } from "enzyme";
|
||||
import { McuParamName } from "farmbot";
|
||||
import { StepsPerMmSettings } from "../steps_per_mm_settings";
|
||||
import { NumericMCUInputGroup } from "../../numeric_mcu_input_group";
|
||||
|
||||
describe("<Motors/>", () => {
|
||||
beforeEach(function () {
|
||||
|
@ -28,7 +30,7 @@ describe("<Motors/>", () => {
|
|||
};
|
||||
|
||||
it("renders the base case", () => {
|
||||
const el = render(<Motors {...fakeProps() } />);
|
||||
const el = render(<Motors {...fakeProps()} />);
|
||||
const txt = el.text();
|
||||
[ // Not a whole lot to test here....
|
||||
"Enable 2nd X Motor",
|
||||
|
@ -88,12 +90,11 @@ describe("<StepsPerMmSettings/>", () => {
|
|||
expect(firstInputProps.setting).toBe("steps_per_mm_x");
|
||||
});
|
||||
|
||||
it("renders mcu settings", () => {
|
||||
fit("renders mcu settings", () => {
|
||||
const p = fakeProps();
|
||||
p.bot.hardware.informational_settings.firmware_version = "5.0.5R";
|
||||
const wrapper = shallow(<StepsPerMmSettings {...p} />);
|
||||
const firstInputProps = wrapper.find("NumericMCUInputGroup")
|
||||
.first().props();
|
||||
const firstInputProps = wrapper.find(NumericMCUInputGroup).first().props();
|
||||
expect(firstInputProps.x).toBe("movement_step_per_mm_x");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,55 +6,13 @@ import { SpacePanelToolTip } from "../space_panel_tool_tip";
|
|||
import { ToggleButton } from "../../../controls/toggle_button";
|
||||
import { settingToggle } from "../../actions";
|
||||
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
||||
import { BotConfigInputBox } from "../bot_config_input_box";
|
||||
import { MotorsProps } from "../interfaces";
|
||||
import { Row, Col } from "../../../ui/index";
|
||||
import { Header } from "./header";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { McuInputBox } from "../mcu_input_box";
|
||||
import { minFwVersionCheck } from "../../../util";
|
||||
|
||||
export function StepsPerMmSettings(props: MotorsProps) {
|
||||
const { dispatch, bot, sourceFbosConfig } = props;
|
||||
const { firmware_version } = bot.hardware.informational_settings;
|
||||
if (minFwVersionCheck(firmware_version, "5.0.5")) {
|
||||
return <NumericMCUInputGroup
|
||||
name={t("Steps per MM")}
|
||||
tooltip={ToolTips.STEPS_PER_MM}
|
||||
x={"movement_step_per_mm_x"}
|
||||
y={"movement_step_per_mm_y"}
|
||||
z={"movement_step_per_mm_z"}
|
||||
bot={bot}
|
||||
dispatch={dispatch} />;
|
||||
} else {
|
||||
return <Row>
|
||||
<Col xs={6}>
|
||||
<label>
|
||||
{t("Steps per MM")}
|
||||
</label>
|
||||
<SpacePanelToolTip tooltip={ToolTips.STEPS_PER_MM} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<BotConfigInputBox
|
||||
setting="steps_per_mm_x"
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
dispatch={dispatch} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<BotConfigInputBox
|
||||
setting="steps_per_mm_y"
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
dispatch={dispatch} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<BotConfigInputBox
|
||||
setting="steps_per_mm_z"
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
dispatch={dispatch} />
|
||||
</Col>
|
||||
</Row>;
|
||||
}
|
||||
}
|
||||
import { StepsPerMmSettings } from "./steps_per_mm_settings";
|
||||
|
||||
export function Motors(props: MotorsProps) {
|
||||
const { dispatch, bot, sourceFbosConfig } = props;
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import * as React from "react";
|
||||
import { BotConfigInputBox } from "../bot_config_input_box";
|
||||
import { MotorsProps } from "../interfaces";
|
||||
import { minFwVersionCheck } from "../../../util";
|
||||
import { ToolTips } from "../../../constants";
|
||||
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
||||
import { Row, Col } from "../../../ui";
|
||||
import { SpacePanelToolTip } from "../space_panel_tool_tip";
|
||||
import { t } from "i18next";
|
||||
|
||||
function LegacyStepsPerMm(props: MotorsProps) {
|
||||
const { dispatch, sourceFbosConfig } = props;
|
||||
|
||||
return <Row>
|
||||
<Col xs={6}>
|
||||
<label>
|
||||
{t("Steps per MM")}
|
||||
</label>
|
||||
<SpacePanelToolTip tooltip={ToolTips.STEPS_PER_MM} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<BotConfigInputBox
|
||||
setting="steps_per_mm_x"
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
dispatch={dispatch} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<BotConfigInputBox
|
||||
setting="steps_per_mm_y"
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
dispatch={dispatch} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<BotConfigInputBox
|
||||
setting="steps_per_mm_z"
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
dispatch={dispatch} />
|
||||
</Col>
|
||||
</Row>;
|
||||
}
|
||||
|
||||
export function StepsPerMmSettings(props: MotorsProps) {
|
||||
const { dispatch, bot } = props;
|
||||
const { firmware_version } = bot.hardware.informational_settings;
|
||||
if (minFwVersionCheck(firmware_version, "5.0.5")) {
|
||||
return <NumericMCUInputGroup
|
||||
name={t("Steps per MM")}
|
||||
tooltip={ToolTips.STEPS_PER_MM}
|
||||
x={"movement_step_per_mm_x"}
|
||||
y={"movement_step_per_mm_y"}
|
||||
z={"movement_step_per_mm_z"}
|
||||
bot={bot}
|
||||
dispatch={dispatch} />;
|
||||
} else {
|
||||
return <LegacyStepsPerMm {...props} />;
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ export function Diagnosis(props: DiagnosisProps) {
|
|||
const diagnosisStatus =
|
||||
props.userMQTT && props.botAPI && props.botMQTT && props.botFirmware;
|
||||
const diagnosisColor = diagnosisStatus ? "green" : "red";
|
||||
const title = diagnosisStatus ? "Ok" : "Error";
|
||||
const title = diagnosisStatus ? t("Ok") : t("Error");
|
||||
return <div>
|
||||
<div className={"connectivity-diagnosis"}>
|
||||
<h4>{t("Diagnosis")}</h4>
|
||||
|
|
|
@ -247,17 +247,17 @@ describe("mapResourcesToCalendar(): regimen farm events", () => {
|
|||
}
|
||||
];
|
||||
|
||||
it("returns calendar rows", () => {
|
||||
fit("returns calendar rows", () => {
|
||||
const testTime = moment("2017-12-15T01:00:00.000Z");
|
||||
const calendar = mapResourcesToCalendar(
|
||||
fakeRegFEResources().index, testTime);
|
||||
const calendar =
|
||||
mapResourcesToCalendar(fakeRegFEResources().index, testTime);
|
||||
expect(calendar.getAll()).toEqual(fakeRegimenFE);
|
||||
});
|
||||
|
||||
it("doesn't return calendar row after event is over", () => {
|
||||
fit("doesn't return calendar row after event is over", () => {
|
||||
const testTime = moment("2017-12-27T01:00:00.000Z");
|
||||
const calendar = mapResourcesToCalendar(
|
||||
fakeRegFEResources().index, testTime);
|
||||
const calendar =
|
||||
mapResourcesToCalendar(fakeRegFEResources().index, testTime);
|
||||
expect(calendar.getAll()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -128,7 +128,6 @@ export function mapStateToPropsAddEdit(props: Everything): AddEditFarmEventProps
|
|||
const regimensById = indexRegimenById(props.resources.index);
|
||||
const sequencesById = indexSequenceById(props.resources.index);
|
||||
const farmEventsById = indexFarmEventById(props.resources.index);
|
||||
|
||||
const farmEvents = selectAllFarmEvents(props.resources.index);
|
||||
|
||||
const getFarmEvent = (): TaggedFarmEvent | undefined => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { browserHistory } from "react-router";
|
||||
export let history = browserHistory;
|
||||
export let push = (url: string) => history.push(url);
|
||||
export let pathname = history.getCurrentLocation().pathname;
|
||||
|
||||
export function getPathArray() {
|
||||
return history.getCurrentLocation().pathname.split("/");
|
||||
}
|
||||
|
|
|
@ -61,10 +61,10 @@ export class HotKeys extends React.Component<Props, Partial<State>> {
|
|||
</div>;
|
||||
}
|
||||
|
||||
toggle = (property: keyof State) => () =>
|
||||
private toggle = (property: keyof State) => () =>
|
||||
this.setState({ [property]: !this.state[property] });
|
||||
|
||||
hotkeys(dispatch: Function, slug: string) {
|
||||
private hotkeys(dispatch: Function, slug: string) {
|
||||
const idx = _.findIndex(links, { slug });
|
||||
const right = "/app/" + (links[idx + 1] || links[0]).slug;
|
||||
const left = "/app/" + (links[idx - 1] || links[links.length - 1]).slug;
|
||||
|
@ -103,7 +103,7 @@ export class HotKeys extends React.Component<Props, Partial<State>> {
|
|||
return hotkeyMap;
|
||||
}
|
||||
|
||||
renderHotkeys() {
|
||||
public renderHotkeys() {
|
||||
const slug = getPathArray()[2];
|
||||
return <Hotkeys>
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import axios from "axios";
|
||||
import { InitOptions } from "i18next";
|
||||
|
||||
/** @public */
|
||||
export function generateUrl(langCode: string) {
|
||||
const lang = langCode.slice(0, 2);
|
||||
const url = "//" + location.host.split(":")
|
||||
|
|
|
@ -12,19 +12,8 @@ export let METHOD_MAP: Dictionary<DataChangeType> = {
|
|||
};
|
||||
export let METHODS = ["post", "put", "patch", "delete"];
|
||||
|
||||
export let RESOURCES: ResourceName[] = [
|
||||
"Point",
|
||||
"Regimen",
|
||||
"Peripheral",
|
||||
"Log",
|
||||
"Sequence",
|
||||
"FarmEvent",
|
||||
"Point",
|
||||
"Device"
|
||||
];
|
||||
|
||||
/** Temporary stub until auto_sync rollout. TODO: Remove */
|
||||
export const RESOURNCE_NAME_IN_URL = [
|
||||
const RESOURNCE_NAME_IN_URL = [
|
||||
"device",
|
||||
"farm_events",
|
||||
"logs",
|
||||
|
|
|
@ -26,6 +26,7 @@ export function responseFulfilled(input: AxiosResponse): AxiosResponse {
|
|||
return input;
|
||||
}
|
||||
|
||||
let ONLY_ONCE = true;
|
||||
export function responseRejected(x: SafeError | undefined) {
|
||||
if (x && isSafeError(x)) {
|
||||
dispatchNetworkUp("user.api");
|
||||
|
@ -50,7 +51,8 @@ export function responseRejected(x: SafeError | undefined) {
|
|||
break;
|
||||
case 451:
|
||||
// DONT REFACTOR: I want to use alert() because it's blocking.
|
||||
alert(t(Content.TOS_UPDATE));
|
||||
ONLY_ONCE && alert(t(Content.TOS_UPDATE));
|
||||
ONLY_ONCE = false;
|
||||
window.location.href = "/tos_update";
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -10,17 +10,6 @@ import { RestResources } from "./resources/interfaces";
|
|||
in the UI. Only certain colors are valid. */
|
||||
export type Color = FarmBotJsColor;
|
||||
|
||||
export interface SelectOptionsParams {
|
||||
label: string;
|
||||
value: string | number | undefined;
|
||||
disabled?: boolean;
|
||||
field?: string;
|
||||
type?: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
z?: number;
|
||||
}
|
||||
|
||||
export interface PinBinding {
|
||||
id?: number;
|
||||
sequence_id: number;
|
||||
|
@ -144,9 +133,3 @@ export type Point =
|
|||
| PlantPointer;
|
||||
|
||||
export type PointerTypeName = Point["pointer_type"];
|
||||
|
||||
export const POINTER_NAMES: Readonly<PointerTypeName>[] = [
|
||||
"Plant",
|
||||
"GenericPointer",
|
||||
"ToolSlot"
|
||||
];
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { t } from "i18next";
|
||||
import { Session } from "./session";
|
||||
import { BooleanSetting } from "./session_keys";
|
||||
|
||||
|
@ -73,7 +74,7 @@ export function LoadingPlant() {
|
|||
fontSize={35}
|
||||
textAnchor="middle"
|
||||
fill="#434343">
|
||||
Loading...
|
||||
{t("Loading...")}
|
||||
</text>
|
||||
</svg>
|
||||
</div>;
|
||||
|
|
|
@ -30,7 +30,7 @@ export const LogsFilterMenu = (props: LogsFilterMenuProps) => {
|
|||
return <fieldset key={logType}>
|
||||
<label>
|
||||
<div className={`saucer ${logType}`} />
|
||||
{_.startCase(logType)}
|
||||
{t(_.startCase(logType))}
|
||||
</label>
|
||||
<button
|
||||
className={"fb-button fb-toggle-button " + btnColor(logType)}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import { TaggedResource } from "./tagged_resources";
|
||||
import { CowardlyDictionary } from "../util";
|
||||
import { ResourceIndex } from "./interfaces";
|
||||
import { assertUuid } from "./selectors";
|
||||
|
||||
interface IndexLookupDictionary<T extends TaggedResource>
|
||||
extends CowardlyDictionary<T> { }
|
||||
|
||||
interface Indexer<T extends TaggedResource> {
|
||||
(index: ResourceIndex): IndexLookupDictionary<T>;
|
||||
}
|
||||
|
||||
interface MapperFn<T extends TaggedResource> {
|
||||
(item: T): T | undefined;
|
||||
}
|
||||
|
||||
/** Build a function,
|
||||
* that returns a function,
|
||||
* that returns a dictionary,
|
||||
* that contains TaggedResource of kind T
|
||||
* that uses the resource's id as the dictionary key.
|
||||
* */
|
||||
export const buildIndexer =
|
||||
<T extends TaggedResource>(kind: T["kind"], mapper?: MapperFn<T>): Indexer<T> => {
|
||||
return function (index: ResourceIndex, ) {
|
||||
const noop: MapperFn<T> = (i) => i;
|
||||
const output: CowardlyDictionary<T> = {};
|
||||
const uuids = index.byKind[kind];
|
||||
const m = mapper || noop;
|
||||
uuids.map(uuid => {
|
||||
assertUuid(kind, uuid);
|
||||
const resource = index.references[uuid];
|
||||
if (resource
|
||||
&& (resource.kind === kind)
|
||||
&& resource.body.id
|
||||
&& m(resource as T)) {
|
||||
output[resource.body.id] = resource as T;
|
||||
}
|
||||
});
|
||||
return output;
|
||||
};
|
||||
};
|
|
@ -29,10 +29,12 @@ import {
|
|||
TaggedDevice,
|
||||
TaggedFbosConfig,
|
||||
TaggedWebAppConfig,
|
||||
SpecialStatus
|
||||
SpecialStatus,
|
||||
TaggedPoint
|
||||
} from "./tagged_resources";
|
||||
import { CowardlyDictionary, betterCompact, sortResourcesById, bail } from "../util";
|
||||
import { isNumber } from "util";
|
||||
import { buildIndexer } from "./selector_support";
|
||||
type StringMap = CowardlyDictionary<string>;
|
||||
|
||||
/** Similar to findId(), but does not throw exceptions. Do NOT use this method
|
||||
|
@ -219,74 +221,17 @@ export function selectAllSequences(index: ResourceIndex) {
|
|||
return findAll(index, "Sequence") as TaggedSequence[];
|
||||
}
|
||||
|
||||
export function indexSequenceById(index: ResourceIndex) {
|
||||
const output: CowardlyDictionary<TaggedSequence> = {};
|
||||
const uuids = index.byKind.Sequence;
|
||||
uuids.map(uuid => {
|
||||
assertUuid("Sequence", uuid);
|
||||
const sequence = index.references[uuid];
|
||||
if (sequence && isTaggedSequence(sequence) && sequence.body.id) {
|
||||
output[sequence.body.id] = sequence;
|
||||
}
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
export function indexRegimenById(index: ResourceIndex) {
|
||||
const output: CowardlyDictionary<TaggedRegimen> = {};
|
||||
|
||||
const uuids = index.byKind.Regimen;
|
||||
uuids.map(uuid => {
|
||||
assertUuid("Regimen", uuid);
|
||||
const regimen = index.references[uuid];
|
||||
if (regimen && isTaggedRegimen(regimen) && regimen.body.id) {
|
||||
output[regimen.body.id] = regimen;
|
||||
}
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
export function indexFarmEventById(index: ResourceIndex) {
|
||||
const output: CowardlyDictionary<TaggedFarmEvent> = {};
|
||||
|
||||
const uuids = index.byKind.FarmEvent;
|
||||
uuids.map(uuid => {
|
||||
assertUuid("FarmEvent", uuid);
|
||||
const farmEvent = index.references[uuid];
|
||||
if (farmEvent && isTaggedFarmEvent(farmEvent) && farmEvent.body.id) {
|
||||
output[farmEvent.body.id] = farmEvent;
|
||||
}
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
export function indexByToolId(index: ResourceIndex) {
|
||||
const output: CowardlyDictionary<TaggedTool> = {};
|
||||
|
||||
const uuids = index.byKind.Tool;
|
||||
uuids.map(uuid => {
|
||||
assertUuid("Tool", uuid);
|
||||
const Tool = index.references[uuid];
|
||||
if (Tool && isTaggedTool(Tool) && Tool.body.id) {
|
||||
output[Tool.body.id] = Tool;
|
||||
}
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
export function indexBySlotId(index: ResourceIndex) {
|
||||
const output: CowardlyDictionary<TaggedToolSlotPointer> = {};
|
||||
|
||||
const uuids = index.byKind.Point;
|
||||
uuids.map(uuid => {
|
||||
assertUuid("Point", uuid);
|
||||
const tool_slot = index.references[uuid];
|
||||
if (tool_slot && isTaggedToolSlotPointer(tool_slot) && tool_slot.body.id) {
|
||||
output[tool_slot.body.id] = tool_slot;
|
||||
}
|
||||
});
|
||||
return output;
|
||||
}
|
||||
const mapper = (i: TaggedPoint): TaggedToolSlotPointer | undefined => {
|
||||
if (i.kind == "Point" && (i.body.pointer_type === "ToolSlot")) {
|
||||
return i as TaggedToolSlotPointer;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
export const indexBySlotId = buildIndexer<TaggedToolSlotPointer>("Point", mapper);
|
||||
export const indexSequenceById = buildIndexer<TaggedSequence>("Sequence");
|
||||
export const indexRegimenById = buildIndexer<TaggedRegimen>("Regimen");
|
||||
export const indexFarmEventById = buildIndexer<TaggedFarmEvent>("FarmEvent");
|
||||
export const indexByToolId = buildIndexer<TaggedTool>("Tool");
|
||||
|
||||
export function assertUuid(expected: ResourceName, actual: string | undefined) {
|
||||
if (actual && !actual.startsWith(expected)) {
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import { App } from "./app";
|
||||
import { crashPage } from "./crash_page";
|
||||
import { RouterState, RedirectFunction } from "react-router";
|
||||
|
||||
/** These methods are a way to determine how to load certain modules
|
||||
* based on the device (mobile or desktop) for optimization/css purposes.
|
||||
*/
|
||||
export function maybeReplaceDesignerModules(next: RouterState,
|
||||
replace: RedirectFunction) {
|
||||
if (next.location.pathname === "/app/designer") {
|
||||
replace(`${next.location.pathname}/plants`);
|
||||
}
|
||||
}
|
||||
|
||||
function page(path: string, getter: () => Promise<React.ReactType>) {
|
||||
return {
|
||||
path,
|
||||
getComponent(_: void, cb: Function) {
|
||||
const ok = (component: React.ReactType) => cb(undefined, component);
|
||||
const no = (e: object) => cb(undefined, crashPage(e));
|
||||
return getter().then(ok, no);
|
||||
}
|
||||
};
|
||||
}
|
||||
const controlsRoute =
|
||||
page("app/controls", async () => (await import("./controls/controls")).Controls);
|
||||
|
||||
export const designerRoutes = {
|
||||
path: "app/designer",
|
||||
onEnter: maybeReplaceDesignerModules,
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/index")
|
||||
.then(module => cb(undefined, module.FarmDesigner))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
childRoutes: [
|
||||
page("plants",
|
||||
async () => (await import("./farm_designer/plants/plant_inventory")).Plants),
|
||||
page("plants/crop_search",
|
||||
async () => (await import("./farm_designer/plants/crop_catalog")).CropCatalog),
|
||||
page("plants/crop_search/:crop",
|
||||
async () => (await import("./farm_designer/plants/crop_info")).CropInfo),
|
||||
page("plants/crop_search/:crop/add",
|
||||
async () => (await import("./farm_designer/plants/add_plant")).AddPlant),
|
||||
page("plants/select",
|
||||
async () => (await import("./farm_designer/plants/select_plants")).SelectPlants),
|
||||
page("plants/move_to", async () => (await import("./farm_designer/plants/move_to")).MoveTo),
|
||||
page("plants/create_point",
|
||||
async () => (await import("./farm_designer/plants/create_points")).CreatePoints),
|
||||
page("plants/:plant_id",
|
||||
async () => (await import("./farm_designer/plants/plant_info")).PlantInfo),
|
||||
page("plants/:plant_id/edit",
|
||||
async () => (await import("./farm_designer/plants/edit_plant_info")).EditPlantInfo),
|
||||
page("farm_events",
|
||||
async () => (await import("./farm_designer/farm_events/farm_events")).FarmEvents),
|
||||
page("farm_events/add",
|
||||
async () => (await import("./farm_designer/farm_events/add_farm_event")).AddFarmEvent),
|
||||
page("farm_events/:farm_event_id",
|
||||
async () => (await import("./farm_designer/farm_events/edit_farm_event")).EditFarmEvent),
|
||||
]
|
||||
};
|
||||
|
||||
export const topLevelRoutes = {
|
||||
component: App,
|
||||
indexRoute: controlsRoute,
|
||||
childRoutes: [
|
||||
page("app/account", async () => (await import("./account/index")).Account),
|
||||
controlsRoute,
|
||||
page("app/device", async () => (await import("./devices/devices")).Devices),
|
||||
page("app/farmware", async () => (await import("./farmware/index")).FarmwarePage),
|
||||
page("app/regimens", async () => (await import("./regimens/index")).Regimens),
|
||||
page("app/regimens/:regimen", async () => (await import("./regimens/index")).Regimens),
|
||||
page("app/sequences", async () => (await import("./sequences/sequences")).Sequences),
|
||||
page("app/sequences/:sequence", async () => (await import("./sequences/sequences")).Sequences),
|
||||
page("app/tools", async () => (await import("./tools/index")).Tools),
|
||||
page("app/logs", async () => (await import("./logs/index")).Logs),
|
||||
page("*", async () => (await import("./404")).FourOhFour),
|
||||
designerRoutes,
|
||||
]
|
||||
};
|
|
@ -1,8 +1,7 @@
|
|||
import "./css/_index.scss";
|
||||
import * as React from "react";
|
||||
import { Provider } from "react-redux";
|
||||
import { Router, RedirectFunction, RouterState } from "react-router";
|
||||
import { App } from "./app";
|
||||
import { Router } from "react-router";
|
||||
import { store as _store } from "./redux/store";
|
||||
import { history } from "./history";
|
||||
import { Store } from "./redux/interfaces";
|
||||
|
@ -10,27 +9,9 @@ import { ready } from "./config/actions";
|
|||
import { Session } from "./session";
|
||||
import { attachToRoot } from "./util";
|
||||
import { Callback } from "i18next";
|
||||
import { crashPage } from "./crash_page";
|
||||
import { topLevelRoutes } from "./route_config";
|
||||
|
||||
const key = "Jan 20 23:52";
|
||||
|
||||
if (!localStorage[key]) {
|
||||
localStorage[key] = JSON.stringify("X");
|
||||
location.reload(true);
|
||||
}
|
||||
|
||||
interface RootComponentProps {
|
||||
store: Store;
|
||||
}
|
||||
|
||||
const controlsRoute = {
|
||||
path: "app/controls",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./controls/controls")
|
||||
.then((module) => cb(undefined, module.Controls))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
}
|
||||
};
|
||||
interface RootComponentProps { store: Store; }
|
||||
|
||||
export const attachAppToDom: Callback = (err, t) => {
|
||||
attachToRoot(RootComponent, { store: _store });
|
||||
|
@ -38,238 +19,6 @@ export const attachAppToDom: Callback = (err, t) => {
|
|||
};
|
||||
|
||||
export class RootComponent extends React.Component<RootComponentProps, {}> {
|
||||
|
||||
requireAuth(_discard: RouterState, replace: RedirectFunction) {
|
||||
const { store } = this.props;
|
||||
if (Session.fetchStoredToken()) { // has a previous session in cache
|
||||
if (store.getState().auth) { // Has session, logged in.
|
||||
return;
|
||||
} else { // Has session but not logged in (returning visitor).
|
||||
store.dispatch(ready());
|
||||
}
|
||||
} else { // Not logged in yet.
|
||||
Session.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/** These methods are a way to determine how to load certain modules
|
||||
* based on the device (mobile or desktop) for optimization/css purposes.
|
||||
* Open to revision.
|
||||
*/
|
||||
maybeReplaceDesignerModules(next: RouterState, replace: RedirectFunction) {
|
||||
if (next.location.pathname === "/app/designer") {
|
||||
replace(`${next.location.pathname}/plants`);
|
||||
}
|
||||
}
|
||||
/*
|
||||
/app => App
|
||||
/app/account => Account
|
||||
/app/controls => Controls
|
||||
/app/device => Devices
|
||||
/app/designer?p1&p2 => FarmDesigner
|
||||
/app/regimens => Regimens
|
||||
/app/sequences => Sequences
|
||||
/app/tools => Tools
|
||||
/app/404 => 404
|
||||
*/
|
||||
|
||||
routes = {
|
||||
component: App,
|
||||
indexRoute: controlsRoute,
|
||||
childRoutes: [
|
||||
{
|
||||
path: "app/account",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./account/index")
|
||||
.then(module => cb(undefined, module.Account))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
}
|
||||
},
|
||||
controlsRoute,
|
||||
{
|
||||
path: "app/device",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./devices/devices")
|
||||
.then(module => cb(undefined, module.Devices))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "app/farmware",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farmware/index")
|
||||
.then(module => cb(undefined, module.FarmwarePage))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "app/designer",
|
||||
onEnter: this.maybeReplaceDesignerModules.bind(this),
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/index")
|
||||
.then(module => cb(undefined, module.FarmDesigner))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
childRoutes: [
|
||||
{
|
||||
path: "plants",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/plants/plant_inventory")
|
||||
.then(module => cb(undefined, module.Plants))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "plants/crop_search",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/plants/crop_catalog")
|
||||
.then(module => cb(undefined, module.CropCatalog))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "plants/crop_search/:crop",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/plants/crop_info")
|
||||
.then(module => cb(undefined, module.CropInfo))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "plants/crop_search/:crop/add",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/plants/add_plant")
|
||||
.then(module => cb(undefined, module.AddPlant))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "plants/select",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/plants/select_plants")
|
||||
.then(module => cb(undefined, module.SelectPlants))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "plants/move_to",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/plants/move_to")
|
||||
.then(module => cb(undefined, module.MoveTo))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "plants/create_point",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/plants/create_points")
|
||||
.then(module => cb(undefined, module.CreatePoints))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "plants/:plant_id",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/plants/plant_info")
|
||||
.then(module => cb(undefined, module.PlantInfo))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "plants/:plant_id/edit",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/plants/edit_plant_info")
|
||||
.then(module => cb(undefined, module.EditPlantInfo))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "farm_events",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/farm_events/farm_events")
|
||||
.then(module => cb(undefined, module.FarmEvents))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "farm_events/add",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/farm_events/add_farm_event")
|
||||
.then(module => cb(undefined, module.AddFarmEvent))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "farm_events/:farm_event_id",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/farm_events/edit_farm_event")
|
||||
.then(module => cb(undefined, module.EditFarmEvent))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "app/regimens",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./regimens/index")
|
||||
.then(module => cb(undefined, module.Regimens))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "app/regimens/:regimen",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./regimens/index")
|
||||
.then(module => cb(undefined, module.Regimens))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "app/sequences",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./sequences/sequences")
|
||||
.then(module => {
|
||||
cb(undefined, module.Sequences);
|
||||
})
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "app/sequences/:sequence",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./sequences/sequences")
|
||||
.then(module => cb(undefined, module.Sequences))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "app/tools",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./tools/index")
|
||||
.then(module => cb(undefined, module.Tools))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "app/logs",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./logs/index")
|
||||
.then(module => cb(undefined, module.Logs))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./404")
|
||||
.then(module => cb(undefined, module.FourOhFour))
|
||||
.catch((e: object) => cb(undefined, crashPage(e)));
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
render() {
|
||||
// ==== TEMPORARY HACK. TODO: Add a before hook, if such a thing exists in
|
||||
// React Router. Or switch routing libs.
|
||||
|
@ -281,7 +30,7 @@ export class RootComponent extends React.Component<RootComponentProps, {}> {
|
|||
// ==== END HACK ====
|
||||
return <Provider store={_store}>
|
||||
<Router history={history}>
|
||||
{this.routes}
|
||||
{topLevelRoutes}
|
||||
</Router>
|
||||
</Provider>;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export class Sequences extends React.Component<Props, {}> {
|
|||
<h3>
|
||||
<i>{t("Sequence Editor")}</i>
|
||||
</h3>
|
||||
<ToolTip helpText={ToolTips.SEQUENCE_EDITOR} />
|
||||
<ToolTip helpText={t(ToolTips.SEQUENCE_EDITOR)} />
|
||||
<SequenceEditorMiddle
|
||||
syncStatus={this.props.syncStatus}
|
||||
dispatch={this.props.dispatch}
|
||||
|
|
|
@ -39,7 +39,7 @@ export function stopIE() {
|
|||
/** Dynamically change the meta title of the page. */
|
||||
export function updatePageInfo(pageName: string) {
|
||||
if (pageName === "designer") { pageName = "Farm Designer"; }
|
||||
document.title = capitalize(pageName);
|
||||
document.title = t(capitalize(pageName));
|
||||
// Possibly add meta "content" here dynamically as well
|
||||
}
|
||||
|
||||
|
|
|
@ -7466,6 +7466,6 @@ yargs@~3.10.0:
|
|||
decamelize "^1.0.0"
|
||||
window-size "0.1.0"
|
||||
|
||||
yarn@^1.2.1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.3.2.tgz#5939762581b5b4ddcd3418c0f6be42df3aee195f"
|
||||
yarn@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.5.1.tgz#e8680360e832ac89521eb80dad3a7bc27a40bab4"
|
||||
|
|
Loading…
Reference in New Issue