Browse Source

Modal cleanup + light page.

webui
Stian Aune 5 years ago
parent
commit
de5fb0d16d
  1. 2
      webui/package.json
  2. 4
      webui/src/App.css
  3. 2
      webui/src/App.js
  4. 2
      webui/src/Components/Group.jsx
  5. 39
      webui/src/Components/Light.jsx
  6. 34
      webui/src/Components/Misc/BrightnessSlider.jsx
  7. 34
      webui/src/Components/Modals/BrightnessModal.jsx
  8. 26
      webui/src/Components/Modals/ColorModal.jsx
  9. 2
      webui/src/Components/Pages/GroupPage.jsx
  10. 3
      webui/src/Components/Pages/IndexPage.jsx
  11. 25
      webui/src/Components/Pages/LightPage.jsx
  12. 17
      webui/src/Helpers/fetcher.js
  13. 6
      webui/src/Helpers/groups.js
  14. 78
      webui/src/Helpers/lights.js
  15. 1
      webui/src/Hooks/auth.js
  16. 2
      webui/src/Hooks/light.js

2
webui/package.json

@ -5,9 +5,7 @@
"dependencies": {
"@jaames/iro": "^4.0.1",
"bootstrap": "^4.2.1",
"rc-color-picker": "^1.2.6",
"react": "^16.8.1",
"react-color": "^2.17.0",
"react-dom": "^16.8.1",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",

4
webui/src/App.css

@ -13,4 +13,8 @@ a, button {
.loading {
margin: 2em 0;
text-align: center;
}
.on-switch {
text-align: center;
}

2
webui/src/App.js

@ -10,6 +10,7 @@ import useAuth from "./Hooks/auth";
import IndexPage from "./Components/Pages/IndexPage";
import GroupPage from "./Components/Pages/GroupPage";
import Loading from "./Components/Loading";
import LightPage from "./Components/Pages/LightPage";
setGlobal({
"auth/login": false,
@ -37,6 +38,7 @@ export default function App() {
{isChecked && isLoggedIn && (
<>
<Route exact path="/" component={IndexPage}/>
<Route exact path="/lights" component={LightPage}/>
<Route exact path="/groups" component={GroupPage}/>
</>
)}

2
webui/src/Components/Group.jsx

@ -20,7 +20,7 @@ function Group({id, name}) {
{" "}
<Button color="secondary">Skift farger</Button>
{" "}
<Button color="danger">Fjern</Button>
{id > 0 && <Button color="danger">Fjern</Button>}
</CardFooter>
</Card>
);

39
webui/src/Components/Light.jsx

@ -1,33 +1,46 @@
import React, {useState} from "react";
import {Badge, Col, ListGroupItem, Row} from "reactstrap";
import {percentage} from "../Helpers/percentage";
import {Badge, ButtonDropdown, Col, DropdownItem, DropdownMenu, DropdownToggle, ListGroupItem, Row} from "reactstrap";
import ColorModal from "./Modals/ColorModal";
import {changeColor} from "../Helpers/lights";
function Light({id, name, color, brightness}) {
const pc = percentage(brightness, 255);
function Light({id, name, on, color, brightness}) {
const [modal, setModal] = useState(false);
const [dropDown, setDropDown] = useState(false);
return (
<ListGroupItem>
<Row>
<Col xs={7}>
{name}
<ButtonDropdown isOpen={dropDown} toggle={() => setDropDown(!dropDown)}>
<DropdownToggle size="sm" caret outline color="secondary">
{name}
</DropdownToggle>
<DropdownMenu>
<DropdownItem>Flytt</DropdownItem>
<DropdownItem>Gi nytt navn</DropdownItem>
<DropdownItem>Glem</DropdownItem>
</DropdownMenu>
</ButtonDropdown>
</Col>
<Col xs={3}>
<Badge style={{backgroundColor: `#${color}`}} onClick={() => setModal(true)}>
{color.toUpperCase()}
</Badge>
{on && (
<Badge style={{backgroundColor: `#${color}`}} onClick={() => setModal(true)}>
{color.toUpperCase()}
</Badge>
)}
</Col>
<Col xs={2}>
<Badge style={{backgroundColor: `gray(${brightness})`}}>{pc}</Badge>
<Badge style={{backgroundColor: `gray(${brightness})`}} onClick={() => setModal(true)}>
{on ? brightness : "Av"}
</Badge>
</Col>
</Row>
{modal && (
<ColorModal value={color}
onConfirm={newColor => {
changeColor(id, newColor);
<ColorModal cValue={color}
bValue={brightness}
pValue={on}
onConfirm={(newColor, newBrightness, newPower) => {
changeColor(id, newColor, newBrightness, newPower);
setModal(false);
}}
onCancel={() => setModal(false)}

34
webui/src/Components/Misc/BrightnessSlider.jsx

@ -0,0 +1,34 @@
import React, {useLayoutEffect} from "react";
import {randId} from "../../Helpers/random";
import iro from "@jaames/iro";
function BrightnessSlider({brightness, onChange}) {
const random = randId();
useLayoutEffect(() => {
console.log(brightness);
const colorPicker = new iro.ColorPicker("#color-picker-" + random, {
color: `rgb(${brightness}, ${brightness}, ${brightness})`,
layout: [
{
component: iro.ui.Slider,
options: {
borderColor: '#888'
}
}
],
});
colorPicker.on("input:end", color => {
onChange(color.rgb.r);
})
}, []);
return (
<div id={`color-picker-${random}`}>
</div>
);
}
export default BrightnessSlider;

34
webui/src/Components/Modals/BrightnessModal.jsx

@ -0,0 +1,34 @@
import React, {useState} from "react";
import {Button, CustomInput, FormGroup, Modal, ModalBody, ModalFooter, ModalHeader} from "reactstrap";
import BrightnessSlider from "../Misc/BrightnessSlider";
function BrightnessModal({bValue, pValue, onConfirm, onCancel}) {
const [brightness, setBrightness] = useState(bValue);
const [power, setPower] = useState(pValue);
return (
<Modal isOpen={true}>
<ModalHeader>Lysstyrke</ModalHeader>
<ModalBody style={{margin: "0 auto"}}>
<FormGroup>
<BrightnessSlider brightness={brightness} onChange={setBrightness}/>
</FormGroup>
<FormGroup>
<CustomInput type="switch"
id="switch-power"
name="power"
label={power ? "På" : "Av"}
checked={power}
onChange={e => setPower(e.target.checked)}/>
</FormGroup>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={() => onConfirm(brightness, power)}>Lagre</Button>
{" "}
<Button color="secondary" onClick={onCancel}>Avbryt</Button>
</ModalFooter>
</Modal>
);
}
export default BrightnessModal;

26
webui/src/Components/Modals/ColorModal.jsx

@ -1,18 +1,34 @@
import React, {useState} from "react";
import {Button, Modal, ModalBody, ModalFooter, ModalHeader} from "reactstrap";
import {Button, CustomInput, FormGroup, Modal, ModalBody, ModalFooter, ModalHeader} from "reactstrap";
import ColorPicker from "../Misc/ColorPicker";
import BrightnessSlider from "../Misc/BrightnessSlider";
function ColorModal({value, onConfirm, onCancel}) {
const [color, setColor] = useState(value);
function ColorModal({cValue, bValue, pValue, onConfirm, onCancel}) {
const [color, setColor] = useState(cValue);
const [brightness, setBrightness] = useState(bValue);
const [power, setPower] = useState(pValue);
return (
<Modal isOpen={true}>
<ModalHeader>Fargevalg</ModalHeader>
<ModalBody style={{margin: "0 auto"}}>
<ColorPicker color={color} onChange={setColor}/>
<FormGroup>
<ColorPicker color={color} onChange={setColor}/>
</FormGroup>
<FormGroup>
<BrightnessSlider brightness={brightness} onChange={setBrightness}/>
</FormGroup>
<FormGroup className="on-switch">
<CustomInput type="switch"
id="switch-power"
name="power"
label={power ? "På" : "Av"}
checked={power}
onChange={e => setPower(e.target.checked)}/>
</FormGroup>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={() => onConfirm(color)}>Lagre</Button>
<Button color="primary" onClick={() => onConfirm(color, brightness, power)}>Lagre</Button>
{" "}
<Button color="secondary" onClick={onCancel}>Avbryt</Button>
</ModalFooter>

2
webui/src/Components/Pages/GroupPage.jsx

@ -1,5 +1,5 @@
import React from "react";
import Groups from "./IndexPage";
import Groups from "../Groups";
function GroupPage() {
return (

3
webui/src/Components/Pages/IndexPage.jsx

@ -1,9 +1,8 @@
import React from "react";
import Groups from "../Groups";
function IndexPage() {
return (
<Groups/>
<div/>
);
}

25
webui/src/Components/Pages/LightPage.jsx

@ -0,0 +1,25 @@
import React from "react";
import useLights from "../../Hooks/light";
import Light from "../Light";
import Loading from "../Loading";
import {Card, CardBody, CardHeader} from "reactstrap";
function LightPage() {
const lights = useLights();
if (lights === null) {
return <Loading/>;
}
return (
<Card className="mt-3">
<CardHeader>
Tilgjengelige lys
</CardHeader>
<CardBody>
{lights.map(light => <Light {...light} />)}
</CardBody>
</Card>
);
}
export default LightPage;

17
webui/src/Helpers/fetcher.js

@ -60,3 +60,20 @@ export function fetchPost(url, data = {}) {
body: JSON.stringify(data),
}).then(authCheck);
}
/**
* @param {string} url
* @param {object} data
* @returns {Promise<object|null>}
*/
export function fetchPatch(url, data = {}) {
return fetch(formatUrl(url), {
method: "PATCH",
credentials: "include",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
body: JSON.stringify(data),
}).then(authCheck);
}

6
webui/src/Helpers/groups.js

@ -28,9 +28,6 @@ export function subscribeToGroup(groupId, callback) {
fetchAll();
}
console.log("subscribed");
console.dir(callbacks);
return callbackId;
}
@ -38,9 +35,6 @@ export function unsubscribeFromGroup(callbackId) {
const callback = callbacks.find(c => c !== null && c.callbackId === callbackId);
const index = callbacks.indexOf(callback);
callbacks[index] = null;
console.log("unsubscribed");
console.dir(callbacks);
}
function fetchAll() {

78
webui/src/Helpers/lights.js

@ -1,6 +1,6 @@
import {randId} from "./random";
import {notNullish, nullish} from "./null";
import {fetchGet} from "./fetcher";
import {nullish} from "./null";
import {fetchGet, fetchPatch} from "./fetcher";
const localData = {};
const callbacks = [];
@ -11,19 +11,11 @@ export function subscribeToLight(lightId, callback) {
callbacks.push({callbackId, lightId, callback});
if (lightId >= 0) {
if (notNullish(localData[lightId])) {
dispatch(localData[lightId]);
}
dispatch();
fetchOne(lightId);
} else {
const list = [];
for (let key in localData) {
if (localData.hasOwnProperty(key) && notNullish(localData[key])) {
list.push(localData[key]);
}
}
dispatch(list);
dispatch();
fetchAll();
}
@ -35,22 +27,64 @@ export function unsubscribeFromLight(callbackId) {
callbacks[index] = null;
}
export function changeColor(lightId, newColor) {
console.dir(localData);
export function changeColor(lightId, newColor, newBrightness, newPower) {
const light = localData[lightId];
if (nullish(light)) {
return;
}
localData[lightId].color = newColor;
dispatch(Object.values(localData));
const oldBrightness = light.brightness;
const oldPower = light.on;
const oldColor = light.color;
light.color = newColor;
light.brightness = newBrightness;
light.on = newPower;
dispatch();
fetchPatch(`/light/${lightId}`, {
color: newColor,
brightness: newBrightness,
on: newPower,
}).then(({data, error}) => {
if (error !== null) {
light.color = oldColor;
light.brightness = oldBrightness;
light.on = oldPower;
dispatch();
return;
}
// TODO: Send request
localData[lightId] = data;
dispatch();
});
}
export function changeBrightness(lightId, newBrightness) {
export function changeBrightness(lightId, newPower, newBrightness) {
const light = localData[lightId];
if (nullish(light)) {
return;
}
const oldBrightness = light.brightness;
const oldPower = light.on;
light.brightness = newBrightness;
light.on = newPower;
dispatch();
fetchPatch(`/light/${lightId}`, {
brightness: newBrightness,
on: newPower,
}).then(({data, error}) => {
if (error !== null) {
light.brightness = oldBrightness;
light.on = oldPower;
dispatch();
return;
}
localData[lightId] = data;
dispatch();
});
}
function fetchAll() {
@ -87,7 +121,11 @@ function handleLight(light) {
dispatch(light);
}
function dispatch(data) {
function dispatch(data = null) {
if (data === null) {
data = Object.values(localData);
}
if (Array.isArray(data)) {
callbacks
.filter(c => c !== null)

1
webui/src/Hooks/auth.js

@ -20,7 +20,6 @@ export default function useAuth() {
fetchPost("/user/login", {username, password}).then(({data, error}) => {
setIsChecked(true);
console.dir({data, error});
if (error !== null) {
setIsLoggedIn(false);
return;

2
webui/src/Hooks/light.js

@ -1,7 +1,7 @@
import {useEffect, useState} from "react";
import {subscribeToLight, unsubscribeFromLight} from "../Helpers/lights";
export default function useLights({groupId = -1, id = -1}) {
export default function useLights({groupId = -1, id = -1} = {}) {
const [light, setLight] = useState(null);
function onChange(light) {

Loading…
Cancel
Save