diff --git a/webpack/__test_support__/fake_state/bot.ts b/webpack/__test_support__/fake_state/bot.ts index 26e89c1e9..847422b53 100644 --- a/webpack/__test_support__/fake_state/bot.ts +++ b/webpack/__test_support__/fake_state/bot.ts @@ -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": { diff --git a/webpack/app.tsx b/webpack/app.tsx index 91d02d726..396395f76 100644 --- a/webpack/app.tsx +++ b/webpack/app.tsx @@ -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; } 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 { !currentPath.startsWith("/app/regimens") && } + axisInversion={this.props.axisInversion} />} ; } } diff --git a/webpack/config/legacy_shims.ts b/webpack/config/legacy_shims.ts index aad8686ff..6b5e18822 100644 --- a/webpack/config/legacy_shims.ts +++ b/webpack/config/legacy_shims.ts @@ -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?"); + } +} diff --git a/webpack/constants.ts b/webpack/constants.ts index f0610d785..69c40a861 100644 --- a/webpack/constants.ts +++ b/webpack/constants.ts @@ -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", diff --git a/webpack/controls/__tests__/move_test.tsx b/webpack/controls/__tests__/move_test.tsx index 22fcb1ed3..e8878c28f 100644 --- a/webpack/controls/__tests__/move_test.tsx +++ b/webpack/controls/__tests__/move_test.tsx @@ -38,7 +38,7 @@ describe("", () => { 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(); const txt = wrapper.text().toLowerCase(); expect(txt).toContain("raw"); @@ -61,10 +61,6 @@ describe("", () => { // 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"); }); diff --git a/webpack/controls/move.tsx b/webpack/controls/move.tsx index f7cde4d3a..1349aded6 100644 --- a/webpack/controls/move.tsx +++ b/webpack/controls/move.tsx @@ -20,22 +20,18 @@ export class Move extends React.Component { 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"; diff --git a/webpack/devices/__tests__/reducer_test.ts b/webpack/devices/__tests__/reducer_test.ts index 4a524b8fb..32c99fac9 100644 --- a/webpack/devices/__tests__/reducer_test.ts +++ b/webpack/devices/__tests__/reducer_test.ts @@ -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); }); diff --git a/webpack/devices/interfaces.ts b/webpack/devices/interfaces.ts index aec2b91a7..76b37b078 100644 --- a/webpack/devices/interfaces.ts +++ b/webpack/devices/interfaces.ts @@ -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; - /** The display setting for encoder data on the controls page. */ - encoder_visibility: Record; + /** + * + * 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; + // /** The display setting for encoder data on the controls page. */ + // encoder_visibility: Record; /** 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; diff --git a/webpack/devices/reducer.ts b/webpack/devices/reducer.ts index 7ed288afa..089d789e8 100644 --- a/webpack/devices/reducer.ts +++ b/webpack/devices/reducer.ts @@ -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(initialState(), afterEach) s.currentFWVersion = payload; return s; }) - .add(Actions.INVERT_JOG_BUTTON, (s, { payload }) => { - s.axis_inversion[payload] = !s.axis_inversion[payload]; - return s; - }) .add(Actions.DISPLAY_ENCODER_DATA, (s, { payload }) => { s.encoder_visibility[payload] = !s.encoder_visibility[payload]; return s; diff --git a/webpack/resources/selectors.ts b/webpack/resources/selectors.ts index 056cb157c..4cad1baf9 100644 --- a/webpack/resources/selectors.ts +++ b/webpack/resources/selectors.ts @@ -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; /** Similar to findId(), but does not throw exceptions. Do NOT use this method @@ -405,12 +407,12 @@ export let findSlotById = byId("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; + } +} diff --git a/webpack/resources/tagged_resources.ts b/webpack/resources/tagged_resources.ts index 7a7c864d3..105441cba 100644 --- a/webpack/resources/tagged_resources.ts +++ b/webpack/resources/tagged_resources.ts @@ -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>; diff --git a/webpack/session.ts b/webpack/session.ts index ebdb7ff13..4711f9dc0 100644 --- a/webpack/session.ts +++ b/webpack/session.ts @@ -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 {