Merge remote-tracking branch 'origin/master' into fork/patch-41

This commit is contained in:
Ranlvor 2020-10-19 18:29:46 +02:00
commit c298a58a47
Signed by untrusted user who does not match committer: Ranlvor
GPG key ID: 5E12D04750EF6F8E
13 changed files with 655 additions and 621 deletions

View file

@ -7,7 +7,8 @@ module.exports = {
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:flowtype/recommended", "plugin:flowtype/recommended",
"plugin:react/recommended" "plugin:react/recommended",
"plugin:react-hooks/recommended"
], ],
"parserOptions": { "parserOptions": {
"ecmaFeatures": { "ecmaFeatures": {

View file

@ -132,7 +132,7 @@ const sliderSVXY = (bulb: string, argument: string) => (
const config: Config = { const config: Config = {
space: { space: {
name: "Home", name: "Home",
color: "orange", color: "teal",
mqtt: "ws://192.168.0.12:1884" mqtt: "ws://192.168.0.12:1884"
}, },
topics: [ topics: [
@ -530,6 +530,11 @@ const config: Config = {
min: 0, min: 0,
max: 255, max: 255,
text: "Helligkeit", text: "Helligkeit",
marks: [
{ value: 1, label: "Dunkel" },
{ value: 120, label: "Medium" },
{ value: 254, label: "Hell" }
],
icon: svg(icons.mdiBrightness7), icon: svg(icons.mdiBrightness7),
topic: "livingroombrightness" topic: "livingroombrightness"
}, },
@ -644,7 +649,7 @@ const config: Config = {
{ {
image: require("./assets/layers/rooms.svg"), image: require("./assets/layers/rooms.svg"),
baseLayer: true, baseLayer: true,
name: "Uwap Home", name: "Rooms",
defaultVisibility: "visible", defaultVisibility: "visible",
opacity: 0.7, opacity: 0.7,
bounds: { bounds: {

View file

@ -38,6 +38,7 @@
"eslint-plugin-flowtype": "^5.2.0", "eslint-plugin-flowtype": "^5.2.0",
"eslint-plugin-fp": "^2.3.0", "eslint-plugin-fp": "^2.3.0",
"eslint-plugin-react": "^7.14.3", "eslint-plugin-react": "^7.14.3",
"eslint-plugin-react-hooks": "^4.1.2",
"file-loader": "^6.1.0", "file-loader": "^6.1.0",
"flow": "^0.2.3", "flow": "^0.2.3",
"flow-bin": "^0.135.0", "flow-bin": "^0.135.0",

View file

@ -124,8 +124,8 @@ class App extends React.PureComponent<AppProps & Classes, AppState> {
for (let i in topics) { for (let i in topics) {
const topic = topics[i]; const topic = topics[i];
const stateTopic = this.topics[topic].state; const stateTopic = this.topics[topic].state;
const parseVal = stateTopic ? stateTopic.type : null; const typeConversion = stateTopic?.type?.from ?? stateTopic?.type;
const val = parseVal == null ? message.toString() : parseVal(message); const val = (typeConversion ?? ((x: Buffer) => x.toString()))(message);
this.setMqttStateDebounced( this.setMqttStateDebounced(
{mqttState: Object.assign({}, {mqttState: Object.assign({},
merge(this.state.mqttState, { [topic]: val}))}); merge(this.state.mqttState, { [topic]: val}))});
@ -145,16 +145,16 @@ class App extends React.PureComponent<AppProps & Classes, AppState> {
this.setState({drawerOpened: false}); this.setState({drawerOpened: false});
} }
changeState = (topic: string, value: string) => { changeState = (topic: string, val: string) => {
try { try {
if (this.topics[topic].command == null) { const commandTopic = this.topics[topic].command;
if (commandTopic == null) {
return; return;
} }
const rawTopic = this.topics[topic].command.name; const rawTopic = commandTopic.name;
const transformValue = this.topics[topic].command.type; const typeConversion = commandTopic?.type?.to ?? commandTopic.type;
const val = const value = (typeConversion ?? Buffer.from)(val);
transformValue == null ? value : transformValue(Buffer.from(value)); this.state.mqttSend(rawTopic, value);
this.state.mqttSend(rawTopic, Buffer.from(val));
} catch (err) { } catch (err) {
this.setState({ error: err.toString() }); this.setState({ error: err.toString() });
} }

View file

@ -6,7 +6,9 @@ import map from "lodash/map";
import filter from "lodash/filter"; import filter from "lodash/filter";
import reduce from "lodash/reduce"; import reduce from "lodash/reduce";
import MqttContext from "mqtt/context"; import MqttContext from "mqtt/context";
import type { Controls, Control, UIControl, ControlUI } from "config/flowtypes"; import type {
Controls, Control, UIControl, ControlUI, Layer
} from "config/flowtypes";
import { renderToString } from "react-dom/server"; import { renderToString } from "react-dom/server";
export type Point = [number, number]; export type Point = [number, number];

View file

@ -10,7 +10,7 @@ import { makeStyles } from "@material-ui/core/styles";
import Tooltip from "@material-ui/core/Tooltip"; import Tooltip from "@material-ui/core/Tooltip";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import ReactIcon from "@mdi/react"; import ReactIcon from "@mdi/react";
import { mdiMap, mdiGithub } from "@mdi/js"; import { mdiMap, mdiGithub, mdiMagnify } from "@mdi/js";
export type TopBarProps = { export type TopBarProps = {
connected: boolean, connected: boolean,
@ -77,7 +77,9 @@ const Search = (props: SearchBarProps) => {
const classes = useSearchStyles(); const classes = useSearchStyles();
return ( return (
<div className={classes.search}> <div className={classes.search}>
<i className={`mdi mdi-magnify ${classes.searchIcon}`}></i> <span className={classes.searchIcon}>
<ReactIcon path={mdiMagnify} size={1} />
</span>
<InputBase placeholder="Search…" type="search" <InputBase placeholder="Search…" type="search"
onChange={(e) => props.onSearch(e.target.value)} onChange={(e) => props.onSearch(e.target.value)}
classes={{ classes={{

View file

@ -16,10 +16,12 @@ const BaseComponent = ({Icon, Label}, item, state, changeState) => (
<Label /> <Label />
<SliderComponent <SliderComponent
value={parseFloat(getValue(item, state))} value={parseFloat(getValue(item, state))}
min={item.min || 0} max={item.max || 100} min={item.min ?? 0} max={item.max ?? 100}
step={item.step || 1} step={item.step}
marks={item.marks ?? false}
onChange={changeSliderValue(item, changeState)} onChange={changeSliderValue(item, changeState)}
disabled={isDisabled(item, state)} disabled={isDisabled(item, state)}
valueLabelDisplay="auto"
style={{marginLeft: 40}} /> style={{marginLeft: 40}} />
</React.Fragment> </React.Fragment>
); );

View file

@ -1,16 +1,22 @@
// @flow // @flow
import type { Icon } from "config/icon"; import type { Icon } from "config/icon";
export type TopicType = (msg: Buffer) => string; export type TopicType = {
from: (msg: Buffer) => string,
to: (newstate: string) => Buffer
};
export type StateCommand = { export type StateTopicType = TopicType | ((msg: Buffer) => string);
export type CommandTopicType = TopicType | ((newstate: string) => Buffer);
export type StateCommand<T> = {
name: string, name: string,
type: TopicType type: T
} }
export type Topic = { export type Topic = {
state?: StateCommand, state?: StateCommand<StateTopicType>,
command?: StateCommand, command?: StateCommand<CommandTopicType>,
defaultValue: string defaultValue: string
}; };
export type Topics = Map<string, Topic>; export type Topics = Map<string, Topic>;
@ -52,9 +58,10 @@ export type UISlider = $ReadOnly<{|
topic: string, topic: string,
icon?: Icon, icon?: Icon,
enableCondition?: (s: State) => boolean, enableCondition?: (s: State) => boolean,
marks?: boolean | Array<{ value: number, label: string}>,
min?: number, min?: number,
max?: number, max?: number,
step?: number step?: ?number
|}>; |}>;
export type UISection = $ReadOnly<{| export type UISection = $ReadOnly<{|
@ -114,6 +121,17 @@ export type Space = {
mqtt: string mqtt: string
}; };
export type Layer = {
image: string,
name: string,
baseLayer?: boolean,
defaultVisibility: "visible" | "hidden",
opacity?: number,
bounds: {
topLeft: Point,
bottomRight: Point
}
};
export type Config = { export type Config = {
space: Space, space: Space,
topics: Topics | Array<Topics>, topics: Topics | Array<Topics>,

View file

@ -34,12 +34,12 @@ const iconChainUtils = <T> (cb: (x: T, p?: IconPropHelper) => Icon,
); );
export const svg = (data: string, props?: IconPropHelper): Icon => { export const svg = (data: string, props?: IconPropHelper): Icon => {
const propColor = ((c: Color | (State) => Color) => (state: State) => { const propColor = ((c: ?Color | (State) => Color) => (state: State) => {
if (typeof c === "function") { if (typeof c === "function") {
return c(state); return c(state);
} }
return c; return c;
})(props?.color ?? "black"); })(props?.color);
return { return {
render: (state) => ( render: (state) => (
<ReactIcon path={data} size={props?.size ?? 1.5} <ReactIcon path={data} size={props?.size ?? 1.5}

View file

@ -1,13 +1,22 @@
// @flow // @flow
import type { TopicType } from "config/flowtypes"; import type { TopicType } from "config/flowtypes";
import at from "lodash/at"; import at from "lodash/at";
import set from "lodash/set";
export const string: TopicType = (msg: Buffer) => msg.toString(); export const string: TopicType = {
from: (msg: Buffer) => msg.toString(),
to: (msg: string) => Buffer.from(msg)
};
export const json = (path: string, innerType?: TopicType): TopicType => { export const json = (path: string, innerType?: TopicType): TopicType => {
const parseAgain = innerType == null ? (x) => x.toString() : innerType; const parseAgain = innerType?.from ?? ((x) => x.toString());
return (msg) => parseAgain(Buffer.from( const parseFirst = innerType?.to ?? ((x) => Buffer.from(x));
at(JSON.parse(msg.toString()), path)[0].toString())); return {
from: (msg) => parseAgain(Buffer.from(
at(JSON.parse(msg.toString()), path)[0].toString())),
to: (msg) => Buffer.from(
JSON.stringify(set({}, path, parseFirst(msg).toString())))
};
}; };
export type TypeOptionParam = { otherwise?: string, [string]: string }; export type TypeOptionParam = { otherwise?: string, [string]: string };
@ -16,13 +25,17 @@ export const option = (values: TypeOptionParam): TopicType => {
if (values.otherwise != null) { if (values.otherwise != null) {
return values.otherwise; return values.otherwise;
} else { } else {
throw new Error( return x;
`Value ${x.toString()} cannot be mapped by the option parameters given`
);
} }
}; };
const mapVal = (x) => (values[x] != null ? values[x] : defaultValue(x)); const mapVal = (x) => (values[x] != null ? values[x] : defaultValue(x));
return (x) => mapVal(x.toString()); return {
from: (x) => mapVal(x.toString()),
to: (x) => Buffer.from(mapVal(x))
};
}; };
export const jsonArray = (msg: Buffer) => JSON.parse(msg.toString()).join(", "); export const jsonArray = {
from: (msg: Buffer) => JSON.parse(msg.toString()).join(", "),
to: (msg: string) => Buffer.from(`[${msg}]`)
};

View file

@ -10,15 +10,3 @@ declare type Classes = {
declare type State = Map<string,string>; declare type State = Map<string,string>;
declare type Point = [number, number]; declare type Point = [number, number];
declare type Layer = {
image: string,
name: string,
baseLayer?: boolean,
defaultVisibility: "visible" | "hidden",
opacity?: number,
bounds: {
topLeft: Point,
bottomRight: Point
}
};

View file

@ -41,7 +41,13 @@ module.exports = env => ({
}, },
plugins: [ plugins: [
new CleanWebpackPlugin(), new CleanWebpackPlugin(),
new WebpackShellPlugin({onBuildStart:preBuildScripts}), new WebpackShellPlugin({
onBuildStart: {
scripts: preBuildScripts,
blocking: true,
parallel: false
}
}),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
title: 'Space Map', title: 'Space Map',
template: 'index.ejs' template: 'index.ejs'

1142
yarn.lock

File diff suppressed because it is too large Load diff