diff --git a/front/src/components/boxs/device-in-room/DeviceRow.jsx b/front/src/components/boxs/device-in-room/DeviceRow.jsx index 7f631e4e1c..cbaab30e1d 100644 --- a/front/src/components/boxs/device-in-room/DeviceRow.jsx +++ b/front/src/components/boxs/device-in-room/DeviceRow.jsx @@ -14,6 +14,9 @@ import SetpointDeviceFeature from './device-features/SetpointDeviceFeature'; import AirConditioningModeDeviceFeature from './device-features/AirConditioningModeDeviceFeature'; import PilotWireModeDeviceFeature from './device-features/PilotWireModeDeviceFeature'; import LMHVolumeDeviceFeature from './device-features/LMHVolumeDeviceFeature'; +import MultiLevelWithInputDeviceFeature from './device-features/MultiLevelWithInputDeviceFeature'; +import SirenModeDeviceFeature from './device-features/SirenModeDeviceFeature'; +import SirenLevelDeviceFeature from './device-features/SirenLevelDeviceFeature'; import PushDeviceFeature from './device-features/PushDeviceFeature'; const ROW_TYPE_BY_FEATURE_TYPE = { @@ -34,8 +37,14 @@ const ROW_TYPE_BY_FEATURE_TYPE = { [DEVICE_FEATURE_TYPES.HEATER.PILOT_WIRE_MODE]: PilotWireModeDeviceFeature, [DEVICE_FEATURE_TYPES.LOCK.BINARY]: BinaryDeviceFeature, [DEVICE_FEATURE_TYPES.SIREN.LMH_VOLUME]: LMHVolumeDeviceFeature, + [DEVICE_FEATURE_TYPES.SIREN.MODE]: SirenModeDeviceFeature, + [DEVICE_FEATURE_TYPES.SIREN.LEVEL]: SirenLevelDeviceFeature, + [DEVICE_FEATURE_TYPES.SIREN.STROBE_LEVEL]: SirenLevelDeviceFeature, + [DEVICE_FEATURE_TYPES.SIREN.STROBE]: BinaryDeviceFeature, + [DEVICE_FEATURE_TYPES.SIREN.STROBE_DUTY_CYCLE]: NumberDeviceFeature, [DEVICE_FEATURE_TYPES.SIREN.MELODY]: NumberDeviceFeature, - [DEVICE_FEATURE_TYPES.DURATION.DECIMAL]: MultiLevelDeviceFeature, + [DEVICE_FEATURE_TYPES.SIREN.VOLUME]: MultiLevelDeviceFeature, + [DEVICE_FEATURE_TYPES.DURATION.DECIMAL]: MultiLevelWithInputDeviceFeature, [DEVICE_FEATURE_TYPES.BUTTON.PUSH]: PushDeviceFeature, [DEVICE_FEATURE_TYPES.SWITCH.TARGET_CURRENT]: SetpointDeviceFeature, [DEVICE_FEATURE_TYPES.ELECTRICAL_VEHICLE_CHARGE.CHARGE_ON]: BinaryDeviceFeature, diff --git a/front/src/components/boxs/device-in-room/SupportedFeatureTypes.jsx b/front/src/components/boxs/device-in-room/SupportedFeatureTypes.jsx index 4167dbae47..46dd108a9f 100644 --- a/front/src/components/boxs/device-in-room/SupportedFeatureTypes.jsx +++ b/front/src/components/boxs/device-in-room/SupportedFeatureTypes.jsx @@ -18,6 +18,7 @@ const SUPPORTED_FEATURE_TYPES = [ DEVICE_FEATURE_TYPES.LOCK.STATE, DEVICE_FEATURE_TYPES.HEATER.PILOT_WIRE_MODE, DEVICE_FEATURE_TYPES.SIREN.LMH_VOLUME, + DEVICE_FEATURE_TYPES.SIREN.VOLUME, DEVICE_FEATURE_TYPES.SIREN.MELODY, DEVICE_FEATURE_TYPES.DURATION.DECIMAL, DEVICE_FEATURE_TYPES.BUTTON.PUSH, diff --git a/front/src/components/boxs/device-in-room/device-features/MultiLevelWithInputDeviceFeature.jsx b/front/src/components/boxs/device-in-room/device-features/MultiLevelWithInputDeviceFeature.jsx new file mode 100644 index 0000000000..651083416c --- /dev/null +++ b/front/src/components/boxs/device-in-room/device-features/MultiLevelWithInputDeviceFeature.jsx @@ -0,0 +1,96 @@ +import { Text } from 'preact-i18n'; +import { useState, useEffect } from 'preact/hooks'; +import get from 'get-value'; +import cx from 'classnames'; + +import { DeviceFeatureCategoriesIcon } from '../../../../utils/consts'; + +import style from './style.css'; + +const MultiLevelWithInputDeviceFeature = ({ children, ...props }) => { + const { deviceFeature } = props; + const [localValue, setLocalValue] = useState(deviceFeature.last_value); + + useEffect(() => { + setLocalValue(deviceFeature.last_value); + }, [deviceFeature.last_value]); + + const clamp = value => { + let v = Number(value); + if (Number.isNaN(v)) { + return deviceFeature.min; + } + if (deviceFeature.min !== undefined && v < deviceFeature.min) v = deviceFeature.min; + if (deviceFeature.max !== undefined && v > deviceFeature.max) v = deviceFeature.max; + return v; + }; + + const handleSlider = e => { + const v = e.target.value; + setLocalValue(v); + props.updateValueWithDebounce(deviceFeature, v); + }; + + const handleInput = e => { + setLocalValue(e.target.value); + }; + + const commitInput = () => { + const v = clamp(localValue); + setLocalValue(v); + props.updateValueWithDebounce(deviceFeature, v); + }; + + const handleKeyDown = e => { + if (e.key === 'Enter') { + e.target.blur(); + } + }; + + return ( + + + + + {props.rowName} + + +
+
+ + {deviceFeature.unit && ( + + + + )} +
+ +
+ + + ); +}; + +export default MultiLevelWithInputDeviceFeature; diff --git a/front/src/components/boxs/device-in-room/device-features/SirenLevelDeviceFeature.jsx b/front/src/components/boxs/device-in-room/device-features/SirenLevelDeviceFeature.jsx new file mode 100644 index 0000000000..164cf8389e --- /dev/null +++ b/front/src/components/boxs/device-in-room/device-features/SirenLevelDeviceFeature.jsx @@ -0,0 +1,46 @@ +import get from 'get-value'; +import { Text } from 'preact-i18n'; + +import { DeviceFeatureCategoriesIcon } from '../../../../utils/consts'; +import { SIREN_LMH_VOLUME } from '../../../../../../server/utils/constants'; + +const SirenLevelDeviceFeature = ({ children, ...props }) => { + const { deviceFeature } = props; + const { category, type } = deviceFeature; + + function updateValue(e) { + props.updateValueWithDebounce(deviceFeature, e.currentTarget.value); + } + + return ( + + + + + {props.rowName} + + +
+
+ +
+
+ + + ); +}; + +export default SirenLevelDeviceFeature; diff --git a/front/src/components/boxs/device-in-room/device-features/SirenModeDeviceFeature.jsx b/front/src/components/boxs/device-in-room/device-features/SirenModeDeviceFeature.jsx new file mode 100644 index 0000000000..56571893c6 --- /dev/null +++ b/front/src/components/boxs/device-in-room/device-features/SirenModeDeviceFeature.jsx @@ -0,0 +1,55 @@ +import get from 'get-value'; +import { Text } from 'preact-i18n'; + +import { DeviceFeatureCategoriesIcon } from '../../../../utils/consts'; +import { SIREN_MODE } from '../../../../../../server/utils/constants'; + +const SirenModeDeviceFeature = ({ children, ...props }) => { + const { deviceFeature } = props; + const { category, type } = deviceFeature; + + function updateValue(e) { + props.updateValueWithDebounce(deviceFeature, e.currentTarget.value); + } + + return ( + + + + + {props.rowName} + + +
+
+ +
+
+ + + ); +}; + +export default SirenModeDeviceFeature; diff --git a/front/src/components/boxs/device-in-room/device-features/style.css b/front/src/components/boxs/device-in-room/device-features/style.css index aa35292ba2..eee0a3f75b 100644 --- a/front/src/components/boxs/device-in-room/device-features/style.css +++ b/front/src/components/boxs/device-in-room/device-features/style.css @@ -56,3 +56,32 @@ input[type='range'][class~='light-temperature']::-ms-fill-lower { .rangeInput { flex: 1; } + +.numericInput { + width: 5rem; +} + +.fullWidthCell { + width: 100%; +} + +.sliderRow { + display: flex; + align-items: center; + justify-content: flex-end; + width: 100%; +} + +.stackedControl { + display: flex; + flex-direction: column; + width: 100%; + align-items: stretch; +} + +.numericRow { + display: flex; + align-items: right; + justify-content: right; + margin-bottom: -0.5rem; +} diff --git a/front/src/components/device/SelectSirenMode.jsx b/front/src/components/device/SelectSirenMode.jsx new file mode 100644 index 0000000000..1224ba47fc --- /dev/null +++ b/front/src/components/device/SelectSirenMode.jsx @@ -0,0 +1,66 @@ +import { Component } from 'preact'; +import { Text } from 'preact-i18n'; +import Select from 'react-select'; +import get from 'get-value'; + +import { SIREN_MODE } from '../../../../server/utils/constants'; +import withIntlAsProp from '../../utils/withIntlAsProp'; + +class SelectSirenMode extends Component { + handleValueChange = ({ value }) => { + this.props.updateValue(value); + }; + + getOptions = () => { + const deviceFeatureOptions = Object.keys(SIREN_MODE).map(key => { + const value = SIREN_MODE[key]; + return { + label: get(this.props.intl.dictionary, `deviceFeatureAction.category.siren.mode.${key.toLowerCase()}`, { + default: key.toLowerCase() + }), + value + }; + }); + + this.setState({ deviceFeatureOptions }); + }; + + getSelectedOption = () => { + const value = this.props.value; + + if (value !== undefined && value !== null && value !== '') { + const numValue = Number(value); + const entry = Object.entries(SIREN_MODE).find(([, v]) => v === numValue); + const key = entry ? entry[0].toLowerCase() : String(value); + return { + label: get(this.props.intl.dictionary, `deviceFeatureAction.category.siren.mode.${key}`, { + default: key + }), + value: numValue + }; + } + return undefined; + }; + + componentDidMount() { + this.getOptions(); + } + + render(props, { deviceFeatureOptions }) { + const selectedOption = this.getSelectedOption(); + return ( +