pull/613/head
Rick Carlino 2018-01-10 14:08:56 -06:00
parent 1c932006d0
commit b3deac0374
12 changed files with 101 additions and 69 deletions

View File

@ -51,15 +51,15 @@ export let bot: Everything["bot"] = {
"farmwares": {}
}
},
axis_inversion: {
"x": false,
"y": false,
"z": false,
},
encoder_visibility: {
"raw_encoders": false,
"scaled_encoders": false,
},
// axis_inversion: {
// "x": false,
// "y": false,
// "z": false,
// },
// encoder_visibility: {
// "raw_encoders": false,
// "scaled_encoders": false,
// },
"dirty": false,
"currentOSVersion": "3.1.6",
"connectivity": {

View File

@ -7,7 +7,7 @@ import { init, error } from "farmbot-toastr";
import { NavBar } from "./nav";
import { Everything, Log } from "./interfaces";
import { LoadingPlant } from "./loading_plant";
import { BotState } from "./devices/interfaces";
import { BotState, Xyz } from "./devices/interfaces";
import { ResourceName, TaggedUser } from "./resources/tagged_resources";
import {
selectAllLogs,
@ -18,6 +18,8 @@ import { HotKeys } from "./hotkeys";
import { ControlsPopup } from "./controls_popup";
import { Content } from "./constants";
import { catchErrors } from "./util";
import { Session } from "./session";
import { BooleanSetting } from "./session_keys";
/** Remove 300ms delay on touch devices - https://github.com/ftlabs/fastclick */
const fastClick = require("fastclick");
@ -35,6 +37,7 @@ export interface AppProps {
consistent: boolean;
autoSyncEnabled: boolean;
timeOffset: number;
axisInversion: Record<Xyz, boolean>;
}
function mapStateToProps(props: Everything): AppProps {
@ -51,7 +54,12 @@ function mapStateToProps(props: Everything): AppProps {
.value(),
loaded: props.resources.loaded,
consistent: !!(props.bot || {}).consistent,
autoSyncEnabled: !!props.bot.hardware.configuration.auto_sync
autoSyncEnabled: !!props.bot.hardware.configuration.auto_sync,
axisInversion: {
x: !!Session.deprecatedGetBool(BooleanSetting.x_axis_inverted),
y: !!Session.deprecatedGetBool(BooleanSetting.y_axis_inverted),
z: !!Session.deprecatedGetBool(BooleanSetting.z_axis_inverted),
}
};
}
/** Time at which the app gives up and asks the user to refresh */
@ -110,7 +118,7 @@ export class App extends React.Component<AppProps, {}> {
!currentPath.startsWith("/app/regimens") &&
<ControlsPopup
dispatch={this.props.dispatch}
axisInversion={this.props.bot.axis_inversion} />}
axisInversion={this.props.axisInversion} />}
</div>;
}
}

View File

@ -1,4 +1,36 @@
import { store } from "../redux/store";
import { getWebAppConfig } from "../resources/selectors";
import { BooleanConfigKey } from "../config_storage/web_app_configs";
import { edit, save } from "../api/crud";
/**
* HISTORICAL CONTEXT: We once stored user settings (like map zoom level) in localStorage and
* would retrieve values via `Session.getBool("zoom_level")`
*
* PROBLEM: localStorage is no longer used. Many parts of the were accessing
* values in places that did not have access to the Redux store.
*
* SOLUTION: Create a temporary shim that will "cheat" and directly call Redux
* store without a lot of boilerplate props passing.
*
* WHY NOT JUST INLINE THESE FUNCTIONS?: It's easier to stub out calls in tests
* that already exist.
*/
/** Avoid using this function in new places. Pass props instead. */
export function getBoolViaRedux(key: BooleanConfigKey): boolean | undefined {
const conf = getWebAppConfig(store.getState().resources.index);
return conf && conf.body[key];
}
/** Avoid using this function in new places. Pass props instead. */
export function setBoolViaRedux(key: BooleanConfigKey, val: boolean) {
const conf = getWebAppConfig(store.getState().resources.index);
if (conf) {
store.dispatch(edit(conf, { [key]: val }));
store.dispatch(save(conf.uuid));
return val;
} else {
throw new Error("Impossible?");
}
}

View File

@ -468,7 +468,6 @@ export enum Actions {
BOT_CHANGE = "BOT_CHANGE",
FETCH_OS_UPDATE_INFO_OK = "FETCH_OS_UPDATE_INFO_OK",
FETCH_FW_UPDATE_INFO_OK = "FETCH_FW_UPDATE_INFO_OK",
INVERT_JOG_BUTTON = "INVERT_JOG_BUTTON",
DISPLAY_ENCODER_DATA = "DISPLAY_ENCODER_DATA",
STASH_STATUS = "STASH_STATUS",

View File

@ -38,7 +38,7 @@ describe("<Move />", () => {
it("has only raw encoder data display", () => {
const p = fakeProps();
p.bot.encoder_visibility.raw_encoders = true;
p.encoder_visibility.raw_encoders = true;
const wrapper = mount(<Move {...p} />);
const txt = wrapper.text().toLowerCase();
expect(txt).toContain("raw");
@ -61,10 +61,6 @@ describe("<Move />", () => {
// tslint:disable-next-line:no-any
const instance = wrapper.instance() as any;
instance.toggle("x")();
expect(p.dispatch).toHaveBeenCalledWith({
type: Actions.INVERT_JOG_BUTTON,
payload: "x"
});
expect(Session.invertBool).toHaveBeenCalledWith("x_axis_inverted");
});

View File

@ -20,22 +20,18 @@ export class Move extends React.Component<MoveProps, {}> {
toggle = (name: Xyz) => () => {
Session.invertBool(INVERSION_MAPPING[name]);
this.props.dispatch({ type: "INVERT_JOG_BUTTON", payload: name });
};
toggle_encoder_data =
(name: EncoderDisplay) => () => {
Session.invertBool(ENCODER_MAPPING[name]);
this.props.dispatch({ type: "DISPLAY_ENCODER_DATA", payload: name });
}
(name: EncoderDisplay) => () => Session.invertBool(ENCODER_MAPPING[name]);
render() {
const {
sync_status, firmware_version
} = this.props.bot.hardware.informational_settings;
const x_axis_inverted = this.props.bot.axis_inversion.x;
const y_axis_inverted = this.props.bot.axis_inversion.y;
const z_axis_inverted = this.props.bot.axis_inversion.z;
const x_axis_inverted = this.props.x_axis_inversion;
const y_axis_inverted = this.props.y_axis_inversion;
const z_axis_inverted = this.props.z_axis_inversion;
const { raw_encoders, scaled_encoders } = this.props.bot.encoder_visibility;
const xBtnColor = x_axis_inverted ? "green" : "red";
const yBtnColor = y_axis_inverted ? "green" : "red";

View File

@ -72,20 +72,21 @@ describe("botRedcuer", () => {
});
it("inverts X/Y/Z", () => {
const action = { type: Actions.INVERT_JOG_BUTTON, payload: "Q" };
pending("These tests are no longer valid");
// const action = { type: Actions.INVERT_JOG_BUTTON, payload: "Q" };
action.payload = "x";
const result = botReducer(initialState(), action);
expect(result.axis_inversion.x)
.toBe(!initialState().axis_inversion.x);
// action.payload = "x";
// const result = botReducer(initialState(), action);
// expect(result.axis_inversion.x)
// .toBe(!initialState().axis_inversion.x);
action.payload = "y";
expect(botReducer(initialState(), action).axis_inversion.y)
.toBe(!initialState().axis_inversion.y);
// action.payload = "y";
// expect(botReducer(initialState(), action).axis_inversion.y)
// .toBe(!initialState().axis_inversion.y);
action.payload = "z";
expect(botReducer(initialState(), action).axis_inversion.z)
.toBe(!initialState().axis_inversion.z);
// action.payload = "z";
// expect(botReducer(initialState(), action).axis_inversion.z)
// .toBe(!initialState().axis_inversion.z);
});

View File

@ -16,7 +16,6 @@ import {
import { RestResources, ResourceIndex } from "../resources/interfaces";
import { TaggedUser } from "../resources/tagged_resources";
import { WD_ENV } from "../farmware/weed_detector/remote_env/interfaces";
import { EncoderDisplay } from "../controls/interfaces";
import { ConnectionStatus, ConnectionState } from "../connectivity/interfaces";
import { IntegerSize } from "../util";
@ -64,19 +63,23 @@ export interface BotState {
* spinner or not. */
isUpdating?: boolean;
controlPanelState: ControlPanelState;
/** The inversions for the jog buttons on the controls page. */
axis_inversion: Record<Xyz, boolean>;
/** The display setting for encoder data on the controls page. */
encoder_visibility: Record<EncoderDisplay, boolean>;
/**
*
* THESE ARE THE RESPONSIBILITY OF THE API SETTINGS STORE NOW. REMOVING
* - RC 10 JAN 18
*
*/
// /** The inversions for the jog buttons on the controls page. */
// axis_inversion: Record<Xyz, boolean>;
// /** The display setting for encoder data on the controls page. */
// encoder_visibility: Record<EncoderDisplay, boolean>;
/** Have all API requests been acknowledged by external services? This flag
* lets us know if it is safe to do data critical tasks with the bot */
consistent: boolean;
connectivity: ConnectionState;
}
export interface BotProp {
bot: BotState;
}
export interface BotProp { bot: BotState; }
/** Status registers for the bot's status */
export type HardwareState = BotStateTree;

View File

@ -85,15 +85,6 @@ export let initialState = (): BotState => ({
dirty: false,
currentOSVersion: undefined,
currentFWVersion: undefined,
axis_inversion: {
x: !!Session.deprecatedGetBool(BooleanSetting.x_axis_inverted),
y: !!Session.deprecatedGetBool(BooleanSetting.y_axis_inverted),
z: !!Session.deprecatedGetBool(BooleanSetting.z_axis_inverted),
},
encoder_visibility: {
raw_encoders: !!Session.deprecatedGetBool(BooleanSetting.raw_encoders),
scaled_encoders: !!Session.deprecatedGetBool(BooleanSetting.scaled_encoders),
},
connectivity: {
"bot.mqtt": undefined,
"user.mqtt": undefined,
@ -188,10 +179,6 @@ export let botReducer = generateReducer<BotState>(initialState(), afterEach)
s.currentFWVersion = payload;
return s;
})
.add<Xyz>(Actions.INVERT_JOG_BUTTON, (s, { payload }) => {
s.axis_inversion[payload] = !s.axis_inversion[payload];
return s;
})
.add<EncoderDisplay>(Actions.DISPLAY_ENCODER_DATA, (s, { payload }) => {
s.encoder_visibility[payload] = !s.encoder_visibility[payload];
return s;

View File

@ -27,9 +27,11 @@ import {
TaggedToolSlotPointer,
TaggedUser,
TaggedWebcamFeed,
TaggedDevice
TaggedDevice,
TaggedWebAppConfig
} from "./tagged_resources";
import { CowardlyDictionary, betterCompact, sortResourcesById } from "../util";
import { isNumber } from "util";
type StringMap = CowardlyDictionary<string>;
/** Similar to findId(), but does not throw exceptions. Do NOT use this method
@ -405,12 +407,12 @@ export let findSlotById = byId<TaggedToolSlotPointer>("Point");
/** Find a Tool's corresponding Slot. */
export let findSlotByToolId = (index: ResourceIndex, tool_id: number) => {
const tool = findToolById(index, tool_id);
const query: any = { body: { tool_id: tool.body.id } };
const query = { body: { tool_id: tool.body.id } };
const every = Object
.keys(index.references)
.map(x => index.references[x]);
const tts = _.find(every, query);
if (tts && isTaggedToolSlotPointer(tts) && sanityCheck(tts)) {
if (tts && !isNumber(tts) && isTaggedToolSlotPointer(tts) && sanityCheck(tts)) {
return tts;
} else {
return undefined;
@ -522,6 +524,7 @@ export function mapToolIdToName(input: ResourceIndex) {
.map(x => ({ key: "" + x.body.id, val: x.body.name }))
.reduce((x, y) => ({ ...{ [y.key]: y.val, ...x } }), {} as StringMap);
}
/** I dislike this method. */
export function findToolBySlotId(input: ResourceIndex, tool_slot_id: number):
TaggedTool | undefined {
@ -546,3 +549,10 @@ export function findToolBySlotId(input: ResourceIndex, tool_slot_id: number):
return undefined;
}
}
export function getWebAppConfig(i: ResourceIndex): TaggedWebAppConfig | undefined {
const conf = i.references[i.byKind.WebAppConfig[0] || "NO"];
if (conf && conf.kind === "WebAppConfig") {
return conf;
}
}

View File

@ -92,7 +92,8 @@ export type TaggedResource =
| TaggedSequence
| TaggedTool
| TaggedUser
| TaggedWebcamFeed;
| TaggedWebcamFeed
| TaggedWebAppConfig;
export type TaggedRegimen = Resource<"Regimen", Regimen>;
export type TaggedTool = Resource<"Tool", Tool>;

View File

@ -1,8 +1,9 @@
import { AuthState } from "./auth/interfaces";
import { box } from "boxed_value";
import { get, isNumber, isBoolean } from "lodash";
import { get, isNumber } from "lodash";
import { BooleanConfigKey, NumberConfigKey } from "./config_storage/web_app_configs";
import { BooleanSetting, NumericSetting } from "./session_keys";
import { getBoolViaRedux, setBoolViaRedux } from "./config/legacy_shims";
/** The `Session` namespace is a wrapper for `localStorage`.
* Use this to avoid direct access of `localStorage` where possible.
@ -44,17 +45,15 @@ export namespace Session {
return undefined as never;
}
/** Fetch a *boolean* value from localstorage. Returns `undefined` when
* none are found.*/
/** @deprecated Don't use this anymore. This is a legacy articfact of when we
* used localStorage to store API settings. */
export function deprecatedGetBool(key: BooleanConfigKey): boolean | undefined {
const output = JSON.parse(localStorage.getItem(key) || "null");
return (isBoolean(output)) ? output : undefined;
return getBoolViaRedux(key);
}
/** Store a boolean value in `localStorage` */
export function setBool(key: BooleanConfigKey, val: boolean): boolean {
localStorage.setItem(key, JSON.stringify(val));
return val;
return setBoolViaRedux(key, val);
}
export function invertBool(key: BooleanConfigKey): boolean {