Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dashboard color picker #945

Merged
merged 4 commits into from
Dec 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions front/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
},
"dependencies": {
"@gladysassistant/gladys-gateway-js": "^3.2.4",
"@jaames/iro": "^5.2.3",
"axios": "^0.18.0",
"classnames": "^2.2.6",
"cropperjs": "^1.5.1",
Expand Down
15 changes: 15 additions & 0 deletions front/src/components/boxs/device-in-room/DeviceRow.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import BinaryDeviceFeature from './device-features/BinaryDeviceFeature';
import ColorDeviceFeature from './device-features/ColorDeviceFeature';
import SensorDeviceFeature from './device-features/SensorDeviceFeature';
import MultilevelDeviceFeature from './device-features/MultiLevelDeviceFeature';

Expand All @@ -24,6 +25,20 @@ const DeviceRow = ({ children, ...props }) => {
/>
);
}
if (props.deviceFeature.type === 'color') {
return (
<ColorDeviceFeature
x={props.x}
y={props.y}
device={props.device}
deviceFeature={props.deviceFeature}
roomIndex={props.roomIndex}
deviceIndex={props.deviceIndex}
deviceFeatureIndex={props.deviceFeatureIndex}
updateValue={props.updateValue}
/>
);
}
if (props.deviceFeature.type === 'dimmer') {
return (
<MultilevelDeviceFeature
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { DEVICE_FEATURE_TYPES } from '../../../../../server/utils/constants';

import actions from '../../../actions/dashboard/edit-boxes/editDevicesInRoom';

const SUPPORTED_FEATURE_TYPES = [DEVICE_FEATURE_TYPES.LIGHT.BINARY, DEVICE_FEATURE_TYPES.LIGHT.COLOR];

@connect('httpClient', actions)
class EditDeviceInRoom extends Component {
updateBoxRoom = room => {
Expand Down Expand Up @@ -40,7 +42,7 @@ class EditDeviceInRoom extends Component {
label: getDeviceFeatureName(this.context.intl.dictionary, device, feature)
};
// for now, we only supports binary on/off and sensors
if (feature.read_only || feature.type === DEVICE_FEATURE_TYPES.LIGHT.BINARY) {
if (feature.read_only || SUPPORTED_FEATURE_TYPES.includes(feature.type)) {
roomDeviceFeatures.push(featureOption);
}
if (this.props.box.device_features && this.props.box.device_features.indexOf(feature.selector) !== -1) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { Component, createRef, Fragment } from 'preact';
import cx from 'classnames';
import iro from '@jaames/iro';

import { intToHex, hexToInt } from '../../../../../../server/utils/colors';
import { getDeviceName } from './utils';

import style from './style.css';

class ColorDeviceType extends Component {
colorPickerRef = createRef();

blur = event => {
if (!event.composedPath().includes(this.colorPickerRef.current.parentElement)) {
this.closeColorPicker(false);
}
};

scroll = () => {
this.colorPickerRef.current.parentElement.scrollIntoView({
behavior: 'smooth'
});
};

closeColorPicker = () => {
this.setColorPickerState(false);
};

toggleColorPicker = () => {
this.setColorPickerState(!this.state.open);
};

setColorPickerState = (open, fromEvent) => {
if (!open) {
document.removeEventListener('click', this.blur, true);
}

this.setState({ open, fromEvent }, () => {
if (this.state.open) {
this.scroll();
document.addEventListener('click', this.blur, true);
Pierre-Gilles marked this conversation as resolved.
Show resolved Hide resolved
}
});
};

updateValue = color => {
const colorInt = hexToInt(color.hexString);
this.props.updateValue(
this.props.x,
this.props.y,
this.props.device,
this.props.deviceFeature,
this.props.deviceIndex,
this.props.deviceFeatureIndex,
colorInt
);
};

constructor(props) {
super(props);

this.blur = this.blur.bind(this);
}

componentDidMount() {
const deviceLastValue = this.props.deviceFeature.last_value;
const color = deviceLastValue === null ? undefined : `#${intToHex(deviceLastValue)}`;

this.colorPicker = new iro.ColorPicker(this.colorPickerRef.current, {
width: 150,
color,
layout: [
{
component: iro.ui.Wheel,
options: {}
}
]
});
this.colorPicker.on('input:end', color => this.updateValue(color));
}

componentWillUnmount() {
document.removeEventListener('click', this.blur, true);
}

render({ device, deviceFeature }, { open }) {
const deviceLastValue = deviceFeature.last_value;
const color = deviceLastValue === null ? undefined : `#${intToHex(deviceLastValue)}`;

return (
<Fragment>
<tr>
<td>
<i class="fe fe-circle" />
</td>
<td>{getDeviceName(device, deviceFeature)}</td>
<td class="text-right">
<div class="m-0 float-right d-flex">
<button
class="btn py-2 border-1 border-dark"
style={{ backgroundColor: color }}
onClick={this.toggleColorPicker}
disabled={open}
/>
</div>
</td>
</tr>
<tr>
<td colSpan="3" class="border-0 p-0">
<div
class={cx('fade', 'w-100', 'mw-100', style.deviceRowPopover, {
'd-none': !open,
popover: open,
show: open
})}
>
<div class="row justify-content-end">
<div class="col-8 py-3 d-flex justify-content-center">
<div ref={this.colorPickerRef} />
</div>
<div class="col-2">
<button class="close m-2" onClick={this.closeColorPicker} />
</div>
</div>
</div>
</td>
</tr>
</Fragment>
);
}
}

export default ColorDeviceType;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.deviceRowPopover {
top: auto;
}
19 changes: 18 additions & 1 deletion front/src/config/demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,13 @@
{
"type": "devices-in-room",
"room": "living-room",
"device_features": ["main-lamp-binary", "tv-lamp-binary", "mqtt-living-room-switch", "mqtt-living-room-temp"]
"device_features": [
"main-lamp-binary",
"tv-lamp-binary",
"tv-lamp-color",
"mqtt-living-room-switch",
"mqtt-living-room-temp"
]
}
],
[
Expand Down Expand Up @@ -241,6 +247,17 @@
"read_only": false,
"last_value": 1,
"last_value_changed": "2019-02-12 07:49:07.556 +00:00"
},
{
"name": "TV Lamp color",
"selector": "tv-lamp-color",
"category": "light",
"type": "color",
"min": 0,
"max": 16777215,
"read_only": false,
"last_value": 65000,
"last_value_changed": "2019-02-12 07:49:07.556 +00:00"
}
]
},
Expand Down
6 changes: 5 additions & 1 deletion server/services/philips-hue/lib/light/light.setValue.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { DEVICE_FEATURE_TYPES } = require('../../../../utils/constants');
const { intToRgb } = require('../../../../utils/colors');

const logger = require('../../../../utils/logger');
const { parseExternalId } = require('../utils/parseExternalId');
Expand All @@ -8,7 +9,7 @@ const { NotFoundError } = require('../../../../utils/coreErrors');
* @description Change value of a Philips hue
* @param {Object} device - The device to control.
* @param {Object} deviceFeature - The binary deviceFeature to control.
* @param {string|number} value - The new value.
* @param {number} value - The new value.
* @example
* turnOff(device, deviceFeature, value);
*/
Expand All @@ -24,6 +25,9 @@ async function setValue(device, deviceFeature, value) {
case DEVICE_FEATURE_TYPES.LIGHT.BINARY:
state = value === 1 ? new this.LightState().on() : new this.LightState().off();
break;
case DEVICE_FEATURE_TYPES.LIGHT.COLOR:
state = new this.LightState().rgb(intToRgb(value));
break;
default:
logger.debug(`Philips Hue : Feature type = "${deviceFeature.type}" not handled`);
break;
Expand Down
12 changes: 4 additions & 8 deletions server/services/tasmota/lib/features/colorChannel.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../../../../utils/constants');
const { intToRgb, rgbToInt } = require('../../../../utils/colors');

module.exports = {
// Tasmota matcher
Expand All @@ -14,23 +15,18 @@ module.exports = {
read_only: false,
has_feedback: true,
min: 0,
max: 16777215,
max: 6579300,
};
}

return null;
},
// Gladys vs Tasmota transformers
readValue: (value) => {
return value.slice(0, 3).reduce((acc, cur, i) => {
return acc + ((cur * 255) / 100) * 256 ** (2 - i);
}, 0);
return rgbToInt(value.slice(0, 3));
},
writeValue: (value) => {
const blue = value % 256;
const green = ((value - blue) / 256) % 256;
const red = ((value - green * 256 - blue) / 65536) % 256;

const [red, green, blue] = intToRgb(value);
return `${red},${green},${blue}`;
},
};
4 changes: 2 additions & 2 deletions server/test/services/philips-hue/index.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const { expect } = require('chai');
const proxyquire = require('proxyquire').noCallThru();
const PhilipsHueClient = require('./mocks.test');
const { MockedPhilipsHueClient } = require('./mocks.test');

const PhilipsHueService = proxyquire('../../../services/philips-hue/index', {
'node-hue-api': PhilipsHueClient,
'node-hue-api': MockedPhilipsHueClient,
});

describe('PhilipsHueService', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const { assert, expect } = require('chai');
const EventEmitter = require('events');
const proxyquire = require('proxyquire').noCallThru();
const PhilipsHueClient = require('../mocks.test');
const { MockedPhilipsHueClient } = require('../mocks.test');

const PhilipsHueService = proxyquire('../../../../services/philips-hue/index', {
'node-hue-api': PhilipsHueClient,
'node-hue-api': MockedPhilipsHueClient,
});

const StateManager = require('../../../../lib/state');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const { expect } = require('chai');
const proxyquire = require('proxyquire').noCallThru();
const PhilipsHueClient = require('../mocks.test');
const { MockedPhilipsHueClient } = require('../mocks.test');

const PhilipsHueService = proxyquire('../../../../services/philips-hue/index', {
'node-hue-api': PhilipsHueClient,
'node-hue-api': MockedPhilipsHueClient,
});

describe('PhilipsHueService', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ const { expect } = require('chai');
const { fake } = require('sinon');
const EventEmitter = require('events');
const proxyquire = require('proxyquire').noCallThru();
const PhilipsHueClient = require('../mocks.test');
const { MockedPhilipsHueClient } = require('../mocks.test');

const PhilipsHueService = proxyquire('../../../../services/philips-hue/index', {
'node-hue-api': PhilipsHueClient,
'node-hue-api': MockedPhilipsHueClient,
});

const StateManager = require('../../../../lib/state');
Expand Down
4 changes: 2 additions & 2 deletions server/test/services/philips-hue/light/light.poll.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ const { assert } = require('chai');
const { fake } = require('sinon');
const EventEmitter = require('events');
const proxyquire = require('proxyquire').noCallThru();
const PhilipsHueClient = require('../mocks.test');
const { MockedPhilipsHueClient } = require('../mocks.test');

const PhilipsHueService = proxyquire('../../../../services/philips-hue/index', {
'node-hue-api': PhilipsHueClient,
'node-hue-api': MockedPhilipsHueClient,
});

const StateManager = require('../../../../lib/state');
Expand Down
Loading