WIP
parent
1c932006d0
commit
b3deac0374
|
@ -51,15 +51,15 @@ export let bot: Everything["bot"] = {
|
||||||
"farmwares": {}
|
"farmwares": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
axis_inversion: {
|
// axis_inversion: {
|
||||||
"x": false,
|
// "x": false,
|
||||||
"y": false,
|
// "y": false,
|
||||||
"z": false,
|
// "z": false,
|
||||||
},
|
// },
|
||||||
encoder_visibility: {
|
// encoder_visibility: {
|
||||||
"raw_encoders": false,
|
// "raw_encoders": false,
|
||||||
"scaled_encoders": false,
|
// "scaled_encoders": false,
|
||||||
},
|
// },
|
||||||
"dirty": false,
|
"dirty": false,
|
||||||
"currentOSVersion": "3.1.6",
|
"currentOSVersion": "3.1.6",
|
||||||
"connectivity": {
|
"connectivity": {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { init, error } from "farmbot-toastr";
|
||||||
import { NavBar } from "./nav";
|
import { NavBar } from "./nav";
|
||||||
import { Everything, Log } from "./interfaces";
|
import { Everything, Log } from "./interfaces";
|
||||||
import { LoadingPlant } from "./loading_plant";
|
import { LoadingPlant } from "./loading_plant";
|
||||||
import { BotState } from "./devices/interfaces";
|
import { BotState, Xyz } from "./devices/interfaces";
|
||||||
import { ResourceName, TaggedUser } from "./resources/tagged_resources";
|
import { ResourceName, TaggedUser } from "./resources/tagged_resources";
|
||||||
import {
|
import {
|
||||||
selectAllLogs,
|
selectAllLogs,
|
||||||
|
@ -18,6 +18,8 @@ import { HotKeys } from "./hotkeys";
|
||||||
import { ControlsPopup } from "./controls_popup";
|
import { ControlsPopup } from "./controls_popup";
|
||||||
import { Content } from "./constants";
|
import { Content } from "./constants";
|
||||||
import { catchErrors } from "./util";
|
import { catchErrors } from "./util";
|
||||||
|
import { Session } from "./session";
|
||||||
|
import { BooleanSetting } from "./session_keys";
|
||||||
|
|
||||||
/** Remove 300ms delay on touch devices - https://github.com/ftlabs/fastclick */
|
/** Remove 300ms delay on touch devices - https://github.com/ftlabs/fastclick */
|
||||||
const fastClick = require("fastclick");
|
const fastClick = require("fastclick");
|
||||||
|
@ -35,6 +37,7 @@ export interface AppProps {
|
||||||
consistent: boolean;
|
consistent: boolean;
|
||||||
autoSyncEnabled: boolean;
|
autoSyncEnabled: boolean;
|
||||||
timeOffset: number;
|
timeOffset: number;
|
||||||
|
axisInversion: Record<Xyz, boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(props: Everything): AppProps {
|
function mapStateToProps(props: Everything): AppProps {
|
||||||
|
@ -51,7 +54,12 @@ function mapStateToProps(props: Everything): AppProps {
|
||||||
.value(),
|
.value(),
|
||||||
loaded: props.resources.loaded,
|
loaded: props.resources.loaded,
|
||||||
consistent: !!(props.bot || {}).consistent,
|
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 */
|
/** 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") &&
|
!currentPath.startsWith("/app/regimens") &&
|
||||||
<ControlsPopup
|
<ControlsPopup
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
axisInversion={this.props.bot.axis_inversion} />}
|
axisInversion={this.props.axisInversion} />}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -468,7 +468,6 @@ export enum Actions {
|
||||||
BOT_CHANGE = "BOT_CHANGE",
|
BOT_CHANGE = "BOT_CHANGE",
|
||||||
FETCH_OS_UPDATE_INFO_OK = "FETCH_OS_UPDATE_INFO_OK",
|
FETCH_OS_UPDATE_INFO_OK = "FETCH_OS_UPDATE_INFO_OK",
|
||||||
FETCH_FW_UPDATE_INFO_OK = "FETCH_FW_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",
|
DISPLAY_ENCODER_DATA = "DISPLAY_ENCODER_DATA",
|
||||||
STASH_STATUS = "STASH_STATUS",
|
STASH_STATUS = "STASH_STATUS",
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ describe("<Move />", () => {
|
||||||
|
|
||||||
it("has only raw encoder data display", () => {
|
it("has only raw encoder data display", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.bot.encoder_visibility.raw_encoders = true;
|
p.encoder_visibility.raw_encoders = true;
|
||||||
const wrapper = mount(<Move {...p} />);
|
const wrapper = mount(<Move {...p} />);
|
||||||
const txt = wrapper.text().toLowerCase();
|
const txt = wrapper.text().toLowerCase();
|
||||||
expect(txt).toContain("raw");
|
expect(txt).toContain("raw");
|
||||||
|
@ -61,10 +61,6 @@ describe("<Move />", () => {
|
||||||
// tslint:disable-next-line:no-any
|
// tslint:disable-next-line:no-any
|
||||||
const instance = wrapper.instance() as any;
|
const instance = wrapper.instance() as any;
|
||||||
instance.toggle("x")();
|
instance.toggle("x")();
|
||||||
expect(p.dispatch).toHaveBeenCalledWith({
|
|
||||||
type: Actions.INVERT_JOG_BUTTON,
|
|
||||||
payload: "x"
|
|
||||||
});
|
|
||||||
expect(Session.invertBool).toHaveBeenCalledWith("x_axis_inverted");
|
expect(Session.invertBool).toHaveBeenCalledWith("x_axis_inverted");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -20,22 +20,18 @@ export class Move extends React.Component<MoveProps, {}> {
|
||||||
|
|
||||||
toggle = (name: Xyz) => () => {
|
toggle = (name: Xyz) => () => {
|
||||||
Session.invertBool(INVERSION_MAPPING[name]);
|
Session.invertBool(INVERSION_MAPPING[name]);
|
||||||
this.props.dispatch({ type: "INVERT_JOG_BUTTON", payload: name });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
toggle_encoder_data =
|
toggle_encoder_data =
|
||||||
(name: EncoderDisplay) => () => {
|
(name: EncoderDisplay) => () => Session.invertBool(ENCODER_MAPPING[name]);
|
||||||
Session.invertBool(ENCODER_MAPPING[name]);
|
|
||||||
this.props.dispatch({ type: "DISPLAY_ENCODER_DATA", payload: name });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
sync_status, firmware_version
|
sync_status, firmware_version
|
||||||
} = this.props.bot.hardware.informational_settings;
|
} = this.props.bot.hardware.informational_settings;
|
||||||
const x_axis_inverted = this.props.bot.axis_inversion.x;
|
const x_axis_inverted = this.props.x_axis_inversion;
|
||||||
const y_axis_inverted = this.props.bot.axis_inversion.y;
|
const y_axis_inverted = this.props.y_axis_inversion;
|
||||||
const z_axis_inverted = this.props.bot.axis_inversion.z;
|
const z_axis_inverted = this.props.z_axis_inversion;
|
||||||
const { raw_encoders, scaled_encoders } = this.props.bot.encoder_visibility;
|
const { raw_encoders, scaled_encoders } = this.props.bot.encoder_visibility;
|
||||||
const xBtnColor = x_axis_inverted ? "green" : "red";
|
const xBtnColor = x_axis_inverted ? "green" : "red";
|
||||||
const yBtnColor = y_axis_inverted ? "green" : "red";
|
const yBtnColor = y_axis_inverted ? "green" : "red";
|
||||||
|
|
|
@ -72,20 +72,21 @@ describe("botRedcuer", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("inverts X/Y/Z", () => {
|
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";
|
// action.payload = "x";
|
||||||
const result = botReducer(initialState(), action);
|
// const result = botReducer(initialState(), action);
|
||||||
expect(result.axis_inversion.x)
|
// expect(result.axis_inversion.x)
|
||||||
.toBe(!initialState().axis_inversion.x);
|
// .toBe(!initialState().axis_inversion.x);
|
||||||
|
|
||||||
action.payload = "y";
|
// action.payload = "y";
|
||||||
expect(botReducer(initialState(), action).axis_inversion.y)
|
// expect(botReducer(initialState(), action).axis_inversion.y)
|
||||||
.toBe(!initialState().axis_inversion.y);
|
// .toBe(!initialState().axis_inversion.y);
|
||||||
|
|
||||||
action.payload = "z";
|
// action.payload = "z";
|
||||||
expect(botReducer(initialState(), action).axis_inversion.z)
|
// expect(botReducer(initialState(), action).axis_inversion.z)
|
||||||
.toBe(!initialState().axis_inversion.z);
|
// .toBe(!initialState().axis_inversion.z);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ import {
|
||||||
import { RestResources, ResourceIndex } from "../resources/interfaces";
|
import { RestResources, ResourceIndex } from "../resources/interfaces";
|
||||||
import { TaggedUser } from "../resources/tagged_resources";
|
import { TaggedUser } from "../resources/tagged_resources";
|
||||||
import { WD_ENV } from "../farmware/weed_detector/remote_env/interfaces";
|
import { WD_ENV } from "../farmware/weed_detector/remote_env/interfaces";
|
||||||
import { EncoderDisplay } from "../controls/interfaces";
|
|
||||||
import { ConnectionStatus, ConnectionState } from "../connectivity/interfaces";
|
import { ConnectionStatus, ConnectionState } from "../connectivity/interfaces";
|
||||||
import { IntegerSize } from "../util";
|
import { IntegerSize } from "../util";
|
||||||
|
|
||||||
|
@ -64,19 +63,23 @@ export interface BotState {
|
||||||
* spinner or not. */
|
* spinner or not. */
|
||||||
isUpdating?: boolean;
|
isUpdating?: boolean;
|
||||||
controlPanelState: ControlPanelState;
|
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. */
|
* THESE ARE THE RESPONSIBILITY OF THE API SETTINGS STORE NOW. REMOVING
|
||||||
encoder_visibility: Record<EncoderDisplay, boolean>;
|
* - 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
|
/** 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 */
|
* lets us know if it is safe to do data critical tasks with the bot */
|
||||||
consistent: boolean;
|
consistent: boolean;
|
||||||
connectivity: ConnectionState;
|
connectivity: ConnectionState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BotProp {
|
export interface BotProp { bot: BotState; }
|
||||||
bot: BotState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Status registers for the bot's status */
|
/** Status registers for the bot's status */
|
||||||
export type HardwareState = BotStateTree;
|
export type HardwareState = BotStateTree;
|
||||||
|
|
|
@ -85,15 +85,6 @@ export let initialState = (): BotState => ({
|
||||||
dirty: false,
|
dirty: false,
|
||||||
currentOSVersion: undefined,
|
currentOSVersion: undefined,
|
||||||
currentFWVersion: 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: {
|
connectivity: {
|
||||||
"bot.mqtt": undefined,
|
"bot.mqtt": undefined,
|
||||||
"user.mqtt": undefined,
|
"user.mqtt": undefined,
|
||||||
|
@ -188,10 +179,6 @@ export let botReducer = generateReducer<BotState>(initialState(), afterEach)
|
||||||
s.currentFWVersion = payload;
|
s.currentFWVersion = payload;
|
||||||
return s;
|
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 }) => {
|
.add<EncoderDisplay>(Actions.DISPLAY_ENCODER_DATA, (s, { payload }) => {
|
||||||
s.encoder_visibility[payload] = !s.encoder_visibility[payload];
|
s.encoder_visibility[payload] = !s.encoder_visibility[payload];
|
||||||
return s;
|
return s;
|
||||||
|
|
|
@ -27,9 +27,11 @@ import {
|
||||||
TaggedToolSlotPointer,
|
TaggedToolSlotPointer,
|
||||||
TaggedUser,
|
TaggedUser,
|
||||||
TaggedWebcamFeed,
|
TaggedWebcamFeed,
|
||||||
TaggedDevice
|
TaggedDevice,
|
||||||
|
TaggedWebAppConfig
|
||||||
} from "./tagged_resources";
|
} from "./tagged_resources";
|
||||||
import { CowardlyDictionary, betterCompact, sortResourcesById } from "../util";
|
import { CowardlyDictionary, betterCompact, sortResourcesById } from "../util";
|
||||||
|
import { isNumber } from "util";
|
||||||
type StringMap = CowardlyDictionary<string>;
|
type StringMap = CowardlyDictionary<string>;
|
||||||
|
|
||||||
/** Similar to findId(), but does not throw exceptions. Do NOT use this method
|
/** 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. */
|
/** Find a Tool's corresponding Slot. */
|
||||||
export let findSlotByToolId = (index: ResourceIndex, tool_id: number) => {
|
export let findSlotByToolId = (index: ResourceIndex, tool_id: number) => {
|
||||||
const tool = findToolById(index, tool_id);
|
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
|
const every = Object
|
||||||
.keys(index.references)
|
.keys(index.references)
|
||||||
.map(x => index.references[x]);
|
.map(x => index.references[x]);
|
||||||
const tts = _.find(every, query);
|
const tts = _.find(every, query);
|
||||||
if (tts && isTaggedToolSlotPointer(tts) && sanityCheck(tts)) {
|
if (tts && !isNumber(tts) && isTaggedToolSlotPointer(tts) && sanityCheck(tts)) {
|
||||||
return tts;
|
return tts;
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -522,6 +524,7 @@ export function mapToolIdToName(input: ResourceIndex) {
|
||||||
.map(x => ({ key: "" + x.body.id, val: x.body.name }))
|
.map(x => ({ key: "" + x.body.id, val: x.body.name }))
|
||||||
.reduce((x, y) => ({ ...{ [y.key]: y.val, ...x } }), {} as StringMap);
|
.reduce((x, y) => ({ ...{ [y.key]: y.val, ...x } }), {} as StringMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** I dislike this method. */
|
/** I dislike this method. */
|
||||||
export function findToolBySlotId(input: ResourceIndex, tool_slot_id: number):
|
export function findToolBySlotId(input: ResourceIndex, tool_slot_id: number):
|
||||||
TaggedTool | undefined {
|
TaggedTool | undefined {
|
||||||
|
@ -546,3 +549,10 @@ export function findToolBySlotId(input: ResourceIndex, tool_slot_id: number):
|
||||||
return undefined;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -92,7 +92,8 @@ export type TaggedResource =
|
||||||
| TaggedSequence
|
| TaggedSequence
|
||||||
| TaggedTool
|
| TaggedTool
|
||||||
| TaggedUser
|
| TaggedUser
|
||||||
| TaggedWebcamFeed;
|
| TaggedWebcamFeed
|
||||||
|
| TaggedWebAppConfig;
|
||||||
|
|
||||||
export type TaggedRegimen = Resource<"Regimen", Regimen>;
|
export type TaggedRegimen = Resource<"Regimen", Regimen>;
|
||||||
export type TaggedTool = Resource<"Tool", Tool>;
|
export type TaggedTool = Resource<"Tool", Tool>;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { AuthState } from "./auth/interfaces";
|
import { AuthState } from "./auth/interfaces";
|
||||||
import { box } from "boxed_value";
|
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 { BooleanConfigKey, NumberConfigKey } from "./config_storage/web_app_configs";
|
||||||
import { BooleanSetting, NumericSetting } from "./session_keys";
|
import { BooleanSetting, NumericSetting } from "./session_keys";
|
||||||
|
import { getBoolViaRedux, setBoolViaRedux } from "./config/legacy_shims";
|
||||||
|
|
||||||
/** The `Session` namespace is a wrapper for `localStorage`.
|
/** The `Session` namespace is a wrapper for `localStorage`.
|
||||||
* Use this to avoid direct access of `localStorage` where possible.
|
* Use this to avoid direct access of `localStorage` where possible.
|
||||||
|
@ -44,17 +45,15 @@ export namespace Session {
|
||||||
return undefined as never;
|
return undefined as never;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fetch a *boolean* value from localstorage. Returns `undefined` when
|
/** @deprecated Don't use this anymore. This is a legacy articfact of when we
|
||||||
* none are found.*/
|
* used localStorage to store API settings. */
|
||||||
export function deprecatedGetBool(key: BooleanConfigKey): boolean | undefined {
|
export function deprecatedGetBool(key: BooleanConfigKey): boolean | undefined {
|
||||||
const output = JSON.parse(localStorage.getItem(key) || "null");
|
return getBoolViaRedux(key);
|
||||||
return (isBoolean(output)) ? output : undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Store a boolean value in `localStorage` */
|
/** Store a boolean value in `localStorage` */
|
||||||
export function setBool(key: BooleanConfigKey, val: boolean): boolean {
|
export function setBool(key: BooleanConfigKey, val: boolean): boolean {
|
||||||
localStorage.setItem(key, JSON.stringify(val));
|
return setBoolViaRedux(key, val);
|
||||||
return val;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertBool(key: BooleanConfigKey): boolean {
|
export function invertBool(key: BooleanConfigKey): boolean {
|
||||||
|
|
Loading…
Reference in New Issue