\n
\n
\n );\n }\n\n drawValue() {\n if (this.props.hideValue) return null;\n return (
{\n Math.round(this.localValue2externalValue(this.state.value)) + this.unit\n }
);\n }\n\n eventToValue(e) {\n let pageY = e.touches ? e.touches[e.touches.length - 1].clientY : e.clientY;\n let pageX = e.touches ? e.touches[e.touches.length - 1].clientX : e.clientX;\n const halfSize = this.knobWidth / 2;\n const value = this.posToValue(pageX - this.rect.left - halfSize, pageY - this.rect.top - halfSize);\n\n this.lastValue = Date.now();\n\n /*this.setState({\n value: value,\n activeTick: this.calcActiveTick(value)\n });*/\n\n this.onValueChange(value);\n }\n\n onMouseMove(e) {\n e.preventDefault();\n e.stopPropagation();\n this.eventToValue(e);\n }\n\n onMouseDown(e) {\n e.preventDefault();\n e.stopPropagation();\n\n this.mouseDown = true;\n\n this.eventToValue(e);\n\n document.addEventListener('mousemove', this.onMouseMoveBind, {passive: false, capture: true});\n document.addEventListener('mouseup', this.onMouseUpBind, {passive: false, capture: true});\n document.addEventListener('touchmove', this.onMouseMoveBind, {passive: false, capture: true});\n document.addEventListener('touchend', this.onMouseUpBind, {passive: false, capture: true});\n }\n\n onMouseUp(e) {\n e.preventDefault();\n e.stopPropagation();\n\n this.mouseDown = false;\n\n console.log('Stopped');\n\n document.removeEventListener('mousemove', this.onMouseMoveBind, {passive: false, capture: true});\n document.removeEventListener('mouseup', this.onMouseUpBind, {passive: false, capture: true});\n document.removeEventListener('touchmove', this.onMouseMoveBind, {passive: false, capture: true});\n document.removeEventListener('touchend', this.onMouseUpBind, {passive: false, capture: true});\n\n this.props.onChange && this.props.onChange(this.localValue2externalValue(this.state.value));\n }\n\n valueToPos(angle) {\n const halfSize = this.rect.width / 2;\n const x = (halfSize + 40) * Math.cos((Math.PI / 180) * angle) + halfSize;\n const y = (halfSize + 40) * Math.sin((Math.PI / 180) * angle) + halfSize;\n return {x, y};\n }\n\n drawMinMax() {\n if (!this.minPos) return;\n\n let styleMin = Object.assign({}, !this.state.value ? activeTitleMin : {}, {left: this.minPos.x, top: this.minPos.y});\n let styleMax = Object.assign({}, this.state.value === 100 ? activeTitleMax : {}, {left: this.maxPos.x, top: this.maxPos.y});\n\n return [\n (
Min ),\n (
Max )\n ];\n\n }\n render() {\n return (\n
\n {this.drawMinMax()}\n {this.drawKnob()}\n\n\n {this.drawValue()}\n
\n {this.drawTicks()}\n
\n
\n );\n }\n}\n\nexport default withStyles(styles)(KnobControl);","/**\n * Copyright 2018 bluefox
\n *\n * Licensed under the Creative Commons Attribution-NonCommercial License, Version 4.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n **/\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport Button from '@material-ui/core/Button';\n\nimport {MdVolumeMute as IconVolume0} from 'react-icons/md';\nimport {MdVolumeUp as IconVolume100} from 'react-icons/md';\n\nimport SmartDialogGeneric from './SmartDialogGeneric';\nimport KnobControl from '../basic-controls/react-knob/KnobControl';\nimport I18n from '../i18n';\n\nclass SmartDialogKnob extends SmartDialogGeneric {\n // expected:\n\n static propTypes = {\n name: PropTypes.oneOfType([\n PropTypes.string,\n PropTypes.object\n ]),\n dialogKey: PropTypes.string,\n windowWidth: PropTypes.number,\n\n onClose: PropTypes.func,\n\n onMute: PropTypes.func,\n\n onValueChange: PropTypes.func,\n startValue: PropTypes.number,\n startMuteValue: PropTypes.bool,\n type: PropTypes.number\n };\n\n static buttonMuteStyle = {\n position: 'absolute',\n left: 'calc(50% + 8em)',\n top: '16.5em',\n height: '2em',\n width: '2.5em',\n background: '-webkit-gradient(linear, left bottom, left top, color-stop(0, #1d1d1d), color-stop(1, #131313))',\n boxShadow: '0 0.2em 0.1em 0.05em rgba(255, 255, 255, 0.1) inset, 0 -0.2em 0.1em 0.05em rgba(0, 0, 0, 0.5) inset, 0 0.5em 0.65em 0 rgba(0, 0, 0, 0.3)',\n color: 'rgb(99, 99, 99)',\n textShadow: '0 0 0.3em rgba(23,23,23)'\n };\n\n static buttonMuted = {\n color: '#a8d8f8'\n };\n\n constructor(props) {\n super(props);\n this.stateRx.value = this.externalValue2localValue(this.props.startValue || 0);\n this.stateRx.muteValue = this.props.startMuteValue || false;\n\n this.dialogStyle = { // used in generic\n background: 'rgba(136,136,136,0.8)',\n maxHeight: 412,\n minHeight: 280,\n };\n\n this.closeOnPaperClick = true; // used in generic\n\n this.componentReady();\n }\n\n componentWillReceiveProps(nextProps) {\n if (nextProps.startValue !== this.state.value) {\n this.setState({value: nextProps.startValue});\n }\n if (nextProps.startMuteValue !== undefined && nextProps.startMuteValue !== this.state.muteValue) {\n this.setState({muteValue: nextProps.startMuteValue});\n }\n }\n\n localValue2externalValue(value) {\n if (this.props.min !== undefined && this.props.max !== undefined) {\n return value * (this.props.max - this.props.min) / 100 + this.props.min;\n } else {\n return value;\n }\n }\n\n externalValue2localValue(value) {\n if (this.props.min !== undefined && this.props.max !== undefined) {\n return ((value - this.props.min) / (this.props.max - this.props.min)) * 100;\n } else {\n return value;\n }\n }\n\n onValueChanged(value) {\n this.click = Date.now();\n\n this.setState({value: this.externalValue2localValue(value)});\n if (this.controlTimer) {\n clearTimeout(this.controlTimer);\n }\n\n if (this.props.onValueChange) {\n this.controlTimer = setTimeout(val => {\n this.controlTimer = null;\n this.props.onValueChange(Math.round(val));\n }, 300, value);\n }\n }\n\n onMute(e) {\n e && e.preventDefault();\n e && e.stopPropagation();\n this.props.onMute && this.props.onMute();\n }\n\n getMuteButton() {\n if (!this.props.onMute) return null;\n const style = Object.assign(\n {},\n SmartDialogKnob.buttonMuteStyle,\n this.state.muteValue ? SmartDialogKnob.buttonMuted : {});\n return (\n \n {this.state.muteValue ? ( ) : ( )}\n );\n }\n\n generateContent() {\n return [\n ( ),\n this.getMuteButton()\n ];\n }\n}\n\nexport default SmartDialogKnob;","/**\n * Copyright 2018 bluefox \n *\n * Licensed under the Creative Commons Attribution-NonCommercial License, Version 4.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n **/\nimport React from 'react';\nimport CircularProgress from '@material-ui/core/CircularProgress';\nimport Button from '@material-ui/core/Button';\n\nimport {MdVolumeMute as IconVolume0} from 'react-icons/md';\nimport {MdVolumeDown as IconVolume50} from 'react-icons/md';\nimport {MdVolumeUp as IconVolume100} from 'react-icons/md';\n\nimport Theme from '../theme';\nimport SmartGeneric from './SmartGeneric';\nimport Types from './SmartTypes';\n//import Dialog from './SmartDialogSlider';\nimport Dialog from '../Dialogs/SmartDialogKnob';\n\nimport I18n from '../i18n';\nimport Icon from \"../icons/Thermometer\";\n\nconst style = {\n mute: {\n fontSize: 'smaller',\n fontWeight: 'normal',\n paddingLeft: 10\n },\n groupText: {\n fontSize: 10,\n paddingLeft: 5\n }\n};\n\nclass SmartVolume extends SmartGeneric {\n constructor(props) {\n super(props);\n if (this.channelInfo.states) {\n let state = this.channelInfo.states.find(state => state.id && state.name === 'SET');\n if (state && this.props.objects[state.id] && this.props.objects[state.id].common) {\n this.id = state.id;\n } else {\n this.id = '';\n }\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'ACTUAL');\n this.actualId = state ? state.id : this.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'MUTE');\n this.muteId = state && state.id;\n\n if (this.channelInfo.type === Types.volumeGroup) {\n this.group = true;\n }\n }\n\n if (this.id) {\n this.max = this.props.objects[this.actualId].common.max;\n this.min = this.props.objects[this.actualId].common.min;\n\n this.props.tile.setState({\n isPointer: true\n });\n\n this.unit = this.props.objects[this.actualId].common.unit;\n this.unit = this.unit ? ' ' + this.unit : '';\n }\n\n this.stateRx.showDialog = false; // support dialog in this tile used in generic class)\n this.stateRx.setValue = null;\n this.key = 'smart-slider-' + this.id + '-';\n this.doubleState = true; // used in generic\n\n this.componentReady();\n }\n\n updateState(id, state) {\n let newState = {};\n\n if (this.actualId === id || (this.id === id && this.id === this.actualId && state.ack)) {\n const val = typeof state.val === 'number' ? state.val : parseFloat(state.val);\n if (!isNaN(val)) {\n newState[id] = val;\n this.setState(newState);\n\n const tileState = val !== this.min;\n this.props.tile.setState({\n state: tileState\n });\n } else {\n newState[id] = null;\n this.setState(newState);\n this.props.tile.setState({\n state: false\n });\n }\n\n // hide desired value\n if (this.state.setValue === newState[id] && state.ack) {\n this.setState({setValue: null});\n }\n\n if (state.ack && this.state.executing) {\n this.setState({executing: false});\n }\n } else if (id === this.id) {\n newState[id] = typeof state.val === 'number' ? state.val : parseFloat(state.val);\n this.setState(newState);\n } else {\n super.updateState(id, state);\n }\n }\n\n setValue(value) {\n console.log('Control ' + this.id + ' = ' + value);\n if (this.actualId !== this.id) {\n this.setState({executing: true, setValue: value});\n }\n if (this.max - this.min > 9) {\n value = Math.round(value);\n }\n this.props.onControl(this.id, value);\n }\n\n toggle() {\n this.props.onControl(this.muteId, !this.state[this.muteId]);\n }\n\n getSecondaryDiv() {\n let Icon;\n let text;\n let color;\n\n if (this.state[this.muteId]) {\n Icon = IconVolume0;\n text = I18n.t('mute');\n color = '#f99';\n } else {\n Icon = IconVolume100;\n text = I18n.t('unmute');\n color = 'inherit'\n }\n let customIcon;\n\n if (this.state.settings.useDefaultIcon) {\n customIcon = ( );\n } else {\n customIcon = ( );\n }\n\n return (\n \n {customIcon}\n \n
);\n }\n\n getIcon() {\n\n let customIcon;\n\n if (this.state.settings.useDefaultIcon) {\n customIcon = ( );\n } else {\n if (this.state.settings.icon) {\n customIcon = ( );\n } else {\n let IconCustom;\n const val = Math.round((this.state[this.actualId] - this.min) / (this.max - this.min) * 100);\n if (val < 25) {\n IconCustom = IconVolume0;\n } else if (val < 75) {\n IconCustom = IconVolume50;\n } else {\n IconCustom = IconVolume100;\n }\n customIcon = ( );\n }\n }\n return (\n \n {customIcon}\n {this.state.executing ? : null}\n
\n );\n }\n\n getStateText() {\n let result = '';\n if (this.state[this.actualId] === null || this.state[this.actualId] === undefined) {\n result = '---';\n } else {\n if (this.workingId && this.state[this.workingId] && this.state.setValue !== null && this.state.setValue !== undefined) {\n if (this.max - this.min > 9) {\n result = Math.round(this.state[this.id]) + this.unit + ' → ' + Math.round(this.state.setValue) + this.unit;\n } else {\n result = this.roundValue(this.state[this.id], 1) + this.unit + ' → ' + this.roundValue(this.state.setValue, 1) + this.unit;\n }\n } else if (this.max - this.min > 9) {\n result = Math.round(this.state[this.id]) + this.unit;\n } else {\n result = this.roundValue(this.state[this.id], 1) + this.unit;\n }\n }\n if (this.muteId && this.state[this.muteId]) {\n result = [({result} ), (({I18n.t('muted')}) )];\n }\n return result;\n }\n\n getAdditionalName() {\n return this.group ? ({I18n.t('group')} ) : null;\n }\n\n render() {\n return this.wrapContent([\n this.getStandardContent(null, true),\n this.muteId && this.getSecondaryDiv(),\n this.state.showDialog ?\n : null\n ]);\n }\n}\n\nexport default SmartVolume;\n\n","/**\n * Copyright 2018 bluefox \n *\n * Licensed under the Creative Commons Attribution-NonCommercial License, Version 4.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n **/\nimport React from 'react';\nimport { withStyles } from '@material-ui/core/styles';\nimport PropTypes from 'prop-types';\nimport Paper from '@material-ui/core/Paper';\nimport Button from '@material-ui/core/Button';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogTitle from '@material-ui/core/DialogTitle';\n\nimport Utils from '../Utils';\nimport SmartDialogGeneric from './SmartDialogGeneric';\nimport I18n from '../i18n';\n\nconst HEIGHT_HEADER = 64;\nconst HEIGHT_CURRENT = 200;\nconst HEIGHT_DAY = 140;\nconst HEIGHT_CHART = 160;\n\nconst styles = {\n 'header-div': {\n width: 'calc(100% - 1em)',\n position: 'relative',\n marginBottom: 16\n },\n 'current-div': {\n height: HEIGHT_CURRENT,\n width: 'calc(100% - 1em)',\n position: 'relative',\n marginBottom: 16,\n overflow: 'hidden'\n },\n 'currentIcon-div': {\n position: 'absolute',\n width: 128,\n height: 128,\n zIndex: 0,\n left: 3,\n top: 24\n },\n 'currentIcon-icon': {\n width: '100%',\n zIndex: 0\n },\n 'currentIcon-temperature': {\n position: 'absolute',\n width: '100%',\n fontSize: 40,\n zIndex: 1,\n fontWeight: 'normal',\n textAlign: 'right',\n color: '#9c9c9c',\n top: 8,\n right: -50\n },\n 'currentDate-div': {\n position: 'absolute',\n zIndex: 1,\n width: 'calc(100% - 2em)',\n top: 16,\n left: 16\n },\n 'currentDate-date': {\n fontWeight: 'normal',\n display: 'inline-block',\n },\n 'currentDate-location': {\n display: 'inline-block',\n position: 'absolute',\n textOverflow: 'ellipsis',\n width: 'calc(100% - 75px)',\n whiteSpace: 'nowrap',\n right: 0,\n textAlign: 'right'\n },\n 'todayTemp-div': {\n position: 'absolute',\n zIndex: 1,\n fontWeight: 'normal',\n top: 35,\n maxWidth: 'calc(100% - 2em - 90px)',\n right: 16,\n textAlign: 'right'\n },\n 'todayTemp-temperature': {\n },\n 'todayTemp-temperatureMin': {\n },\n 'todayTemp-temperatureMax': {\n fontWeight: 'bold'\n },\n 'todayTemp-temperatureTitle': {\n },\n 'todayTemp-temperatureValue': {\n },\n 'todayTemp-precipitation': {\n },\n 'todayTemp-precipitationTitle': {\n },\n 'todayTemp-precipitationValue': {\n paddingLeft: 2\n },\n 'todayTemp-pressure': {\n },\n 'todayTemp-pressureTitle': {\n },\n 'todayTemp-pressureValue': {\n paddingLeft: 2\n },\n 'todayState-div': {\n position: 'absolute',\n zIndex: 1,\n width: 'calc(100% - 90px)',\n fontWeight: 'normal',\n bottom: 16,\n left: 118,\n textAlign: 'left',\n fontSize: 14\n },\n 'todayState-wind': {\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n },\n 'todayState-windTitle': {\n },\n 'todayState-windDir': {\n marginLeft: 2,\n },\n 'todayState-windSpeed': {\n marginLeft: 2\n },\n 'todayState-windIcon': {\n display: 'inline-block',\n marginLeft: 5\n },\n 'todayState-windChill': {\n },\n 'todayState-windChillTitle': {\n paddingRight: 5\n },\n 'todayState-windChillValue': {\n },\n 'todayState-humidity': {\n },\n 'todayState-humidityTitle': {\n paddingRight: 5\n },\n 'todayState-humidityValue': {\n },\n 'todayState-state': {\n },\n 'chart-div': {\n height: HEIGHT_CHART,\n width: 'calc(100% - 1em)',\n overflowX: 'hidden',\n overflowY: 'auto',\n marginBottom: 16,\n padding: '0 16px',\n cursor: 'pointer'\n },\n 'chart-header': {\n width: '100%',\n fontSize: 16,\n paddingTop: 16,\n fontWeight: 'bold'\n },\n 'chart-img': {\n width: 'calc(100% - 16px)',\n height: 'calc(100% - 40px)',\n backgroundSize: 'contain',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'center center',\n },\n 'chart-dialog': {\n zIndex: 2101\n },\n 'chart-dialog-paper': {\n width: 'calc(100% - 4em)',\n maxWidth: 'calc(100% - 4em)',\n height: 'calc(100% - 4em)',\n maxHeight: 'calc(100% - 4em)',\n },\n 'chart-dialog-content': {\n width: 'calc(100% - 5em)',\n height: 'calc(100% - 4em)',\n marginLeft: '1em',\n backgroundSize: 'contain',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'center center',\n },\n\n 'days-div': {\n height: 'calc(100% - ' + HEIGHT_CURRENT + 'px - ' + HEIGHT_HEADER + 'px)',\n width: 'calc(100% - 1em)',\n overflowX: 'hidden',\n overflowY: 'auto'\n },\n 'day-div': {\n height: HEIGHT_DAY,\n width: '100%',\n marginBottom: 16,\n position: 'relative'\n },\n 'dayIcon-div': {\n position: 'absolute',\n width: 90,\n height: 90,\n zIndex: 0,\n left: 16,\n top: 30,\n backgroundSize: 'contain',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'center center',\n },\n 'dayIcon-icon': {\n width: '100%',\n zIndex: 0\n },\n 'dayIcon-temperature': {\n position: 'absolute',\n width: '100%',\n fontSize: 40,\n zIndex: 1,\n fontWeight: 'normal',\n textAlign: 'right',\n color: '#9c9c9c',\n top: 8,\n right: -50\n },\n 'dayDate-div': {\n position: 'absolute',\n zIndex: 1,\n width: 'calc(100% - 2em)',\n top: 16,\n left: 16\n },\n 'dayDate-date': {\n fontWeight: 'bold',\n display: 'inline-block',\n },\n 'dayTemp-div': {\n position: 'absolute',\n zIndex: 1,\n fontWeight: 'normal',\n top: 35,\n maxWidth: 'calc(100% - 2em - 90px)',\n right: 16,\n textAlign: 'right'\n },\n 'dayTemp-temperature': {\n },\n 'dayTemp-temperatureMin': {\n },\n 'dayTemp-temperatureMax': {\n fontWeight: 'bold'\n },\n 'dayTemp-temperatureTitle': {\n },\n 'dayTemp-temperatureValue': {\n },\n 'dayTemp-precipitation': {\n },\n 'dayTemp-precipitationTitle': {\n },\n 'dayTemp-precipitationValue': {\n paddingLeft: 2\n },\n 'dayTemp-humidity': {\n },\n 'dayTemp-humidityTitle': {\n },\n 'dayTemp-humidityValue': {\n paddingLeft: 2\n },\n 'dayTemp-pressure': {\n },\n 'dayTemp-pressureTitle': {\n },\n 'dayTemp-pressureValue': {\n paddingLeft: 2\n },\n 'dayState-div': {\n position: 'absolute',\n zIndex: 1,\n width: 'calc(100% - 90px)',\n fontWeight: 'normal',\n bottom: 16,\n left: 118,\n textAlign: 'left'\n },\n 'dayState-wind': {\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n fontSize: 14\n },\n 'dayState-windTitle': {\n },\n 'dayState-windDir': {\n marginLeft: 2,\n },\n 'dayState-windSpeed': {\n marginLeft: 2\n },\n 'dayState-windIcon': {\n display: 'inline-block',\n marginLeft: 5,\n width: 16,\n maxHeight: 16\n },\n 'dayState-windChill': {\n },\n 'dayState-windChillTitle': {\n paddingRight: 5\n },\n 'dayState-windChillValue': {\n },\n 'dayState-state': {\n fontSize: 14\n },\n\n};\n\nclass SmartDialogWeatherForecast extends SmartDialogGeneric {\n // expected:\n static propTypes = {\n name: PropTypes.oneOfType([\n PropTypes.string,\n PropTypes.object\n ]),\n dialogKey: PropTypes.string.isRequired,\n windowWidth: PropTypes.number,\n onClose: PropTypes.func.isRequired,\n objects: PropTypes.object,\n states: PropTypes.object,\n onCollectIds: PropTypes.func,\n enumNames: PropTypes.array,\n ids: PropTypes.object.isRequired,\n settings: PropTypes.object\n };\n\n constructor(props) {\n super(props);\n\n this.ids = this.props.ids;\n\n for (const id in this.ids.current) {\n if (this.ids.current.hasOwnProperty(id) && this.ids.current[id]) {\n this.subscribes = this.subscribes || [];\n if (this.ids.current[id] instanceof Array) {\n this.ids.current[id].forEach(i => this.subscribes.push(i));\n } else {\n this.subscribes.push(this.ids.current[id]);\n }\n }\n }\n for (let d = 0; d < this.ids.days.length; d++) {\n if (!this.ids.days[d]) continue;\n for (const id in this.ids.days[d]) {\n if (this.ids.days[d].hasOwnProperty(id) && this.ids.days[d][id]) {\n this.subscribes = this.subscribes || [];\n this.subscribes.push(this.ids.days[d][id]);\n }\n }\n }\n if (this.props.settings) {\n if (this.props.settings.tempID && this.subscribes.indexOf(this.props.settings.tempID) === -1) {\n this.subscribes = this.subscribes || [];\n this.subscribes.push(this.props.settings.tempID);\n }\n if (this.props.settings.humidityID && this.subscribes.indexOf(this.props.settings.humidityID) === -1) {\n this.subscribes = this.subscribes || [];\n this.subscribes.push(this.props.settings.humidityID);\n }\n }\n\n this.stateRx.chartOpened = false;\n\n this.setMaxHeight();\n\n const enums = [];\n this.props.enumNames.forEach(e => (enums.indexOf(e) === -1) && enums.push(e));\n if (enums.indexOf(this.props.name) === -1) {\n enums.push(this.props.name);\n }\n this.name = enums.join(' / ');\n this.collectState = null;\n this.collectTimer = null;\n\n this.componentReady();\n }\n\n setMaxHeight(states) {\n let maxHeight = 0;\n states = states || this.state;\n\n this.divs = {\n 'header': {height: HEIGHT_HEADER, visible: true},\n 'current': {height: HEIGHT_CURRENT, visible: true},\n 'chart': {height: HEIGHT_CHART,\n visible: states && states[this.ids.current.chart] && !states[this.ids.current.chart].match(/\\.html$|\\.htm/)}\n };\n for (let d = 0; d < this.ids.days.length; d++) {\n if (this.ids.days[d]) {\n this.divs['day' + d] = {height: HEIGHT_DAY, visible: true};\n }\n }\n // calculate positions\n for (const name in this.divs) {\n if (this.divs.hasOwnProperty(name) && this.divs[name].visible) {\n maxHeight += this.divs[name].height + 16;\n }\n }\n\n if (this.dialogStyle.maxHeight !== maxHeight) {\n this.dialogStyle = {maxHeight: maxHeight};\n }\n }\n\n onUpdateTimer() {\n this.collectTimer = null;\n if (this.collectState) {\n this.setMaxHeight(this.collectState);\n this.setState(this.collectState);\n this.collectState = null;\n }\n }\n\n updateState(id, state) {\n for (let d = 0; d < this.ids.days.length; d++) {\n if (!this.ids.days[d]) continue;\n if (id === this.ids.days[d].temperature ||\n id === this.ids.days[d].humidity ||\n id === this.ids.days[d].humidityMin ||\n id === this.ids.days[d].humidityMax ||\n id === this.ids.days[d].windChill ||\n id === this.ids.days[d].windSpeed ||\n id === this.ids.days[d].temperatureMin ||\n id === this.ids.days[d].temperatureMax ||\n id === this.ids.days[d].precipitation ||\n id === this.ids.days[d].pressure) {\n const val = Math.round(parseFloat(state.val));\n if (!isNaN(val)) {\n this.collectState = this.collectState || {};\n this.collectState[id] = val;\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n }\n return;\n } else\n if (id === this.ids.days[d].icon ||\n id === this.ids.days[d].state) {\n this.collectState = this.collectState || {};\n this.collectState[id] = state.val || '';\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n return;\n } else\n if (id === this.ids.days[d].icon ||\n id === this.ids.days[d].state ||\n id === this.ids.days[d].windIcon) {\n this.collectState = this.collectState || {};\n this.collectState[id] = state.val || '';\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n return;\n } else if (id === this.ids.days[d].windDirection) {\n let dir = '';\n if (state && state.val !== null && state.val !== '' && state.val !== undefined && (typeof state.val === 'number' || parseInt(state.val, 10).toString() === state.val.toString())) {\n dir = I18n.t('wind_' + Utils.getWindDirection(state.val)).replace('wind_', '');\n } else {\n dir = state.val;\n }\n\n this.collectState = this.collectState || {};\n this.collectState[id] = dir;\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n return;\n } else if (id === this.ids.days[d].date) {\n let date = '';\n\n if (state && state.val) {\n date = Utils.date2string(state.val) || '';\n }\n\n this.collectState = this.collectState || {};\n this.collectState[id] = date;\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n return;\n }\n }\n\n if (id === this.ids.current.temperature ||\n id === this.ids.current.humidity ||\n id === this.ids.current.windChill ||\n id === this.ids.current.windSpeed ||\n id === this.props.settings.tempID ||\n id === this.props.settings.humidityID ||\n id === this.ids.current.temperatureMin ||\n id === this.ids.current.temperatureMax ||\n id === this.ids.current.precipitation ||\n id === this.ids.current.pressure) {\n const val = Math.round(parseFloat(state.val));\n if (!isNaN(val)) {\n this.collectState = this.collectState || {};\n this.collectState[id] = val;\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n }\n } else\n if (id === this.ids.current.icon ||\n id === this.ids.current.history ||\n id === this.ids.current.chart ||\n id === this.ids.current.state ||\n id === this.ids.current.windIcon) {\n this.collectState = this.collectState || {};\n this.collectState[id] = state.val || '';\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else\n if (this.ids.current.location && this.ids.current.location.indexOf(id) !== -1) {\n this.collectState = this.collectState || {};\n if (state.val && state.val.replace(/[,.-]/g, '').trim()) {\n this.collectState.location = state.val || '';\n }\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else if (id === this.ids.current.windDirection) {\n let dir = '';\n if (state && state.val !== null && state.val !== '' && state.val !== undefined && (typeof state.val === 'number' || parseInt(state.val, 10).toString() === state.val.toString())) {\n dir = I18n.t('wind_' + Utils.getWindDirection(state.val)).replace('wind_', '');\n } else {\n dir = state.val;\n }\n\n this.collectState = this.collectState || {};\n this.collectState[id] = dir;\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else if (id === this.ids.current.date) {\n let date = '';\n\n if (state && state.val) {\n date = Utils.date2string(state.val) || '';\n }\n\n this.collectState = this.collectState || {};\n this.collectState[id] = date;\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else {\n console.log(id + ' => ' + state.val);\n super.updateState(id, state);\n }\n }\n\n getHeaderDiv() {\n if (!this.divs.header.visible) return null;\n return ({this.name}
);\n }\n\n getDayIconDiv(d) {\n const classes = this.props.classes;\n const temp = this.ids.days[d].temperature && this.state[this.ids.days[d].temperature];\n const icon = this.ids.days[d].icon && this.state[this.ids.days[d].icon];\n\n if (!temp && !icon) return null;\n\n return (\n {icon ? (
) : null}\n {temp !== null && temp !== undefined ? (
{temp}°
) : null}\n
);\n }\n\n getDayDateDiv(d) {\n const classes = this.props.classes;\n let date = this.ids.days[d].date && this.state[this.ids.days[d].date];\n if (!date) {\n const now = new Date();\n now.setDate(now.getDate() + d);\n date = Utils.date2string(now);\n }\n\n return ();\n }\n\n getDayWindDiv(d) {\n const classes = this.props.classes;\n let windChill = this.ids.days[d].windchill && this.state[this.ids.days[d].date];\n let windDir = this.ids.days[d].windDirection && this.state[this.ids.days[d].windDirection];\n if (windDir !== null && windDir !== undefined && (typeof windDir === 'number' || parseInt(windDir, 10) === windDir)) {\n windDir = I18n.t('wind_' + Utils.getWindDirection(windDir)).replace('wind_', '');\n }\n let windSpeed = this.ids.days[d].windSpeed && this.state[this.ids.days[d].windSpeed];\n let windIcon = this.ids.days[d].windIcon && this.state[this.ids.days[d].windIcon];\n\n let state = this.ids.days[d].state && this.state[this.ids.days[d].state];\n\n if (!state && !windChill && windChill !== 0 && !windDir && !windSpeed && windSpeed !== 0) {\n return null;\n }\n\n return (\n\n {windChill !== null && windChill !== undefined ?\n (
\n {I18n.t('Windchill')}: \n {windChill} \n
)\n : null}\n\n {(windDir !== null && windDir !== undefined) || (windSpeed !== null && windSpeed !== undefined) ?\n (
\n
{I18n.t('Wind')}: \n {windIcon ? (
) : null}\n {windDir ? (
{windDir} ) : null}\n {windSpeed !== null && windSpeed !== undefined && !isNaN(windSpeed) ? (
{windSpeed}{this.props.windUnit} ) : null}\n
)\n : null}\n\n {state ? (
{state}
) : null}\n
);\n }\n\n getDayTempDiv(d) {\n const classes = this.props.classes;\n let tempMin = this.ids.days[d].temperatureMin && this.state[this.ids.days[d].temperatureMin];\n let tempMax = this.ids.days[d].temperatureMax && this.state[this.ids.days[d].temperatureMax];\n let precipitation = this.ids.days[d].precipitation && this.state[this.ids.days[d].precipitation];\n let pressure = this.ids.days[d].pressure && this.state[this.ids.days[d].pressure];\n let humidity = this.ids.days[d].humidity && this.state[this.ids.days[d].humidity];\n\n if (!humidity && humidity !== 0 &&\n this.ids.current.humidity &&\n this.props.objects[this.ids.current.humidity] &&\n this.props.objects[this.ids.current.humidity].common &&\n this.props.objects[this.ids.current.humidity].common.role &&\n this.props.objects[this.ids.current.humidity].common.role.indexOf('forecast.0') !== -1) {\n humidity = this.state[this.ids.current.humidity];\n }\n if (!tempMin && tempMin !== 0 &&\n this.ids.current.temperatureMin) {\n const obj = this.props.objects[this.ids.current.temperatureMin];\n if (obj &&\n obj.common &&\n obj.common.role &&\n obj.common.role.indexOf('forecast.0') !== -1) {\n tempMin = this.state[this.ids.current.temperatureMin];\n }\n }\n if (!tempMax && tempMax !== 0 &&\n this.ids.current.temperatureMax) {\n const obj = this.props.objects[this.ids.current.temperatureMax];\n if (obj &&\n obj.common &&\n obj.common.role &&\n obj.common.role.indexOf('forecast.0') !== -1) {\n tempMax = this.state[this.ids.current.temperatureMax];\n }\n }\n\n let temp;\n if (tempMin !== null && tempMin !== undefined &&\n tempMax !== null && tempMax !== undefined && tempMin !== tempMax) {\n temp = [\n ({tempMax}° ),\n ( / ),\n ({tempMin}° )\n ];\n } else if (\n (tempMin !== null && tempMin !== undefined) ||\n (tempMax !== null && tempMax !== undefined)) {\n if (tempMin === null || tempMin === undefined) {\n tempMin = tempMax;\n }\n temp = ({tempMin}° );\n }\n\n if (!temp &&\n !precipitation && precipitation !== 0 &&\n !pressure && pressure !== 0 &&\n !humidity && humidity !== 0) {\n return null;\n }\n\n return (\n {temp !== null && temp !== undefined ?\n (
\n {temp} \n
)\n : null}\n\n {precipitation !== null && precipitation !== undefined ?\n (
\n {I18n.t('Precip.')}: \n {precipitation}% \n
)\n : null}\n\n {humidity !== null && humidity !== undefined ?\n (
\n {I18n.t('Humidity')}: \n {humidity}% \n
)\n : null}\n {pressure !== null && pressure !== undefined ?\n (
\n {I18n.t('Pressure')}: \n {pressure}{this.props.pressureUnit} \n
)\n : null}\n
);\n }\n\n getDayDiv(d) {\n if (!this.ids.days[d]) return null;\n let parts = [\n this.getDayIconDiv(d),\n this.getDayDateDiv(d),\n this.getDayWindDiv(d),\n this.getDayTempDiv(d),\n ];\n if (!parts[0] && !parts[2] && !parts[3]) {\n return null;\n }\n\n return ({parts} );\n }\n\n getCurrentIconDiv() {\n const classes = this.props.classes;\n let temp;\n temp = this.props.settings.tempID && this.state[this.props.settings.tempID];\n if (!temp && temp !== 0) {\n temp = this.ids.current.temperature && this.state[this.ids.current.temperature];\n }\n\n return (\n
\n {temp !== null && temp !== undefined ? (
{temp}°
) : null}\n
);\n }\n\n getCurrentDateLocationDiv() {\n const classes = this.props.classes;\n let date = this.ids.current.date && this.state[this.ids.current.date];\n let location = this.props.settings.locationText;\n location = location || this.state.location;\n location = location || I18n.t('Weather');\n date = date || Utils.date2string(new Date());\n\n return (\n
{date},
\n
{location}
\n
);\n }\n\n getTodayWindDiv() {\n const classes = this.props.classes;\n let windChill = this.ids.current.windchill && this.state[this.ids.current.date];\n let windDir = this.ids.current.windDirection && this.state[this.ids.current.windDirection];\n if (windDir !== null && windDir !== undefined && (typeof windDir === 'number' || parseInt(windDir, 10) === windDir)) {\n windDir = I18n.t('wind_' + Utils.getWindDirection(windDir)).replace('wind_', '');\n }\n let windSpeed = this.ids.current.windSpeed && this.state[this.ids.current.windSpeed];\n let windIcon = this.ids.current.windIcon && this.state[this.ids.current.windIcon];\n let humidity = this.props.settings.humidityID && this.state[this.props.settings.humidityID];\n if (!humidity && humidity !== 0) {\n humidity = this.ids.current.humidity && this.state[this.ids.current.humidity];\n }\n\n let state = this.ids.current.state && this.state[this.ids.current.state];\n return (\n\n {windChill !== null && windChill !== undefined ?\n (
\n {I18n.t('Windchill')}: \n {windChill} \n
)\n : null}\n\n {(windDir !== null && windDir !== undefined) || (windSpeed !== null && windSpeed !== undefined) ?\n (
\n
{I18n.t('Wind')}: \n {windIcon ? (
) : null}\n {windDir ? (
{windDir} ) : null}\n {windSpeed !== null && windSpeed !== undefined && !isNaN(windSpeed) ? (
{windSpeed}{this.props.windUnit} ) : null}\n
)\n : null}\n\n {humidity || humidity === 0 ?\n (
\n {I18n.t('Humidity')}: \n {humidity}% \n
)\n : null}\n\n {state ? (
{state}
) : null}\n
);\n }\n\n getTodayTempDiv() {\n const classes = this.props.classes;\n let tempMin = this.ids.current.temperatureMin && this.state[this.ids.current.temperatureMin];\n let tempMax = this.ids.current.temperatureMax && this.state[this.ids.current.temperatureMax];\n let precipitation = this.ids.current.precipitation && this.state[this.ids.current.precipitation];\n let pressure = this.ids.current.pressure && this.state[this.ids.current.pressure];\n\n let temp;\n if (tempMin !== null && tempMin !== undefined &&\n tempMax !== null && tempMax !== undefined) {\n temp = [\n ({tempMax}° ),\n ( / ),\n ({tempMin}° )\n ];\n }\n\n return (\n {temp !== null && temp !== undefined ?\n (
\n {temp} \n
)\n : null}\n\n {precipitation !== null && precipitation !== undefined ?\n (
\n {I18n.t('Precip.')}: \n {precipitation}% \n
)\n : null}\n\n {pressure !== null && pressure !== undefined ?\n (
\n {I18n.t('Pressure')}: \n {pressure}{this.props.pressureUnit} \n
)\n : null}\n
);\n }\n\n getChartDiv() {\n const classes = this.props.classes;\n let chart = this.ids.current.chart && this.state[this.ids.current.chart];\n if (chart && chart.toLowerCase().match(/\\.svg$|\\.jpg$|\\.jpeg$|\\.gif$|\\.png$/)) {\n if (chart.indexOf('?') === -1) {\n chart += '?ts=' + Date.now();\n } else {\n chart += '&ts=' + Date.now();\n }\n return [\n ( this.setState({chartOpened: true})}>\n {I18n.t('Chart')}
\n
\n ),\n this.state.chartOpened ? ( this.setState({chartOpened: false})}\n className={this.props.classes['chart-dialog']}\n aria-labelledby=\"alert-dialog-title\"\n aria-describedby=\"alert-dialog-description\"\n >\n {I18n.t('Chart')} \n \n \n this.setState({chartOpened: false})} color=\"primary\" autoFocus>{I18n.t('Close')} \n \n ) : null];\n } else {\n return null;\n }\n }\n\n getDaysDiv() {\n let days = this.ids.days.map(function (day, d) {\n if (!d && this.props.settings.hideFirstDay) return null;\n return day && this.getDayDiv(d);\n }.bind(this));\n if (this.props.settings.chartLast) {\n days.push(this.getChartDiv());\n } else {\n days.unshift(this.getChartDiv());\n }\n days = days.filter(day => day);\n if (days.length) {\n return ({days}
);\n } else {\n return null;\n }\n }\n\n onOpenHistory() {\n if (this.ids.current.chart && this.state[this.ids.current.chart]) {\n const win = window.open(this.state[this.ids.current.chart], '_blank');\n win.focus();\n }\n }\n\n getCurrentDiv() {\n return (\n this.onOpenHistory()}>\n {this.getCurrentIconDiv()}\n {this.getCurrentDateLocationDiv()}\n {this.getTodayWindDiv()}\n {this.getTodayTempDiv()}\n );\n }\n\n generateContent() {\n return [\n this.getHeaderDiv(),\n this.getCurrentDiv(),\n this.getDaysDiv()\n ];\n }\n}\n\nexport default withStyles(styles)(SmartDialogWeatherForecast);","/**\n * Copyright 2018 bluefox \n *\n * Licensed under the Creative Commons Attribution-NonCommercial License, Version 4.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n **/\nimport React from 'react';\nimport { withStyles } from '@material-ui/core/styles';\nimport PropTypes from 'prop-types';\n\nimport SmartGeneric from './SmartGeneric';\nimport Utils from '../Utils';\nimport Dialog from '../Dialogs/SmartDialogWeatherForecast';\nimport I18n from '../i18n';\n\nconst styles = {\n 'currentIcon-div': {\n position: 'absolute',\n width: 90,\n height: 90,\n zIndex: 0,\n left: 3,\n top: 24\n },\n 'currentIcon-icon': {\n width: '100%',\n zIndex: 0\n },\n 'currentIcon-temperature': {\n position: 'absolute',\n width: '100%',\n fontSize: 40,\n zIndex: 1,\n fontWeight: 'normal',\n textAlign: 'right',\n color: '#9c9c9c',\n top: 8,\n right: -50\n },\n 'currentDate-div': {\n position: 'absolute',\n zIndex: 1,\n width: 'calc(100% - 2rem)',\n top: 16,\n left: 16\n },\n 'currentDate-date': {\n fontWeight: 'normal',\n display: 'inline-block',\n },\n 'currentDate-location': {\n display: 'inline-block',\n position: 'absolute',\n textOverflow: 'ellipsis',\n width: 'calc(100% - 78px)',\n whiteSpace: 'nowrap',\n right: 0,\n textAlign: 'right'\n },\n 'todayTemp-div': {\n position: 'absolute',\n zIndex: 1,\n fontWeight: 'normal',\n top: 35,\n maxWidth: 'calc(100% - 2rem - 90px)',\n right: 16,\n textAlign: 'right'\n },\n 'todayTemp-temperature': {\n },\n 'todayTemp-temperatureMin': {\n },\n 'todayTemp-temperatureMax': {\n fontWeight: 'bold'\n },\n 'todayTemp-temperatureTitle': {\n },\n 'todayTemp-temperatureValue': {\n },\n 'todayTemp-precipitation': {\n },\n 'todayTemp-precipitationTitle': {\n },\n 'todayTemp-precipitationValue': {\n paddingLeft: 2\n },\n 'todayState-div': {\n position: 'absolute',\n zIndex: 1,\n width: 'calc(100% - 90px)',\n fontWeight: 'normal',\n bottom: 16,\n left: 80,\n textAlign: 'left'\n },\n 'todayState-wind': {\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n fontSize: 14\n },\n 'todayState-windTitle': {\n },\n 'todayState-windDir': {\n marginLeft: 2,\n },\n 'todayState-windSpeed': {\n marginLeft: 2\n },\n 'todayState-windIcon': {\n display: 'inline-block',\n marginLeft: 5,\n width: 16,\n maxHeight: 16\n },\n 'todayState-windChill': {\n },\n 'todayState-windChillTitle': {\n paddingRight: 5\n },\n 'todayState-windChillValue': {\n },\n 'todayState-state': {\n fontSize: 14\n }\n};\n\nclass SmartWeatherForecast extends SmartGeneric {\n static propTypes = {\n classes: PropTypes.object.isRequired\n };\n\n constructor(props) {\n super(props);\n this.ids = {\n current: {\n date: null,\n location: null,\n icon: null,\n state: null,\n temperature: null,\n humidity: null,\n windChill: null,\n windSpeed: null,\n windIcon: null,\n windDirection: null,\n pressure: null,\n precipitation: null\n },\n days: [\n {\n date: null,\n icon: null,\n state: null,\n temperature: null,\n temperatureMin: null,\n temperatureMax: null,\n humidity: null,\n humidityMin: null,\n humidityMax: null,\n windSpeed: null,\n windIcon: null,\n windDirection: null,\n precipitation: null,\n pressure: null\n }\n ]\n };\n\n this.collectState = null;\n this.collectTimer = null;\n\n if (this.channelInfo.states) {\n //console.log(JSON.stringify(this.channelInfo.states, null, 2));\n\n // Actual\n let state = this.channelInfo.states.find(state => state.id && state.name === 'ICON');\n if (state) {\n this.id = state.id;\n this.ids.current.icon = state.id;\n } else {\n this.id = '';\n }\n state = this.channelInfo.states.find(state => state.id && state.name === 'TEMP');\n this.ids.current.temperature = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'STATE');\n this.ids.current.state = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'DATE');\n this.ids.current.date = state && state.id;\n\n if (!this.ids.current.date) {\n state = this.channelInfo.states.find(state => state.id && state.name === 'DOW');\n this.ids.current.date = state && state.id;\n }\n\n state = this.channelInfo.states.filter(state => state.id && state.name === 'LOCATION').map(state => state.id);\n this.ids.current.location = state && state.length && state;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'HUMIDITY');\n this.ids.current.humidity = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'PRESSURE');\n this.ids.current.pressure = state && state.id;\n this.pressureUnit = '';\n if (this.ids.current.pressure) {\n const obj = this.props.objects[this.ids.current.pressure];\n if (obj && obj.common && obj.common.unit) {\n this.pressureUnit = ' ' + I18n.t(obj.common.unit);\n }\n }\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'WIND_SPEED');\n this.ids.current.windSpeed = state && state.id;\n this.windUnit = '';\n if (this.ids.current.windSpeed) {\n const obj = this.props.objects[this.ids.current.windSpeed];\n if (obj && obj.common && obj.common.unit) {\n this.windUnit = ' ' + I18n.t(obj.common.unit);\n }\n }\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'WIND_CHILL');\n this.ids.current.windChill = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'FEELS_LIKE');\n if (!this.ids.current.windChill) {\n this.ids.current.windChill = state && state.id;\n } else if (state) {\n const pos = this.subscribes.indexOf(state.id);\n this.subscribes.splice(pos, 1);\n state.id = null;\n }\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'WIND_ICON');\n this.ids.current.windIcon = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'WIND_DIRECTION');\n this.ids.current.windDirection = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'WIND_DIRECTION_STR');\n if (!this.ids.current.windDirection) {\n this.ids.current.windDirection = state && state.id;\n } else if (state) {\n const pos = this.subscribes.indexOf(state.id);\n this.subscribes.splice(pos, 1);\n state.id = null;\n }\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'TEMP_MIN');\n this.ids.current.temperatureMin = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'TEMP_MAX');\n this.ids.current.temperatureMax = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'PRECIPITATION_CHANCE');\n this.ids.current.precipitation = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'HISTORY_CHART');\n this.ids.current.history = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'FORECAST_CHART');\n this.ids.current.chart = state && state.id;\n\n for (let d = 0; d < 6; d++) {\n this.ids.days[d] = this.ids.days[d] || {};\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'DATE' + d);\n this.ids.days[d].date = state && state.id;\n\n if (!this.ids.days[d].date) {\n state = this.channelInfo.states.find(state => state.id && state.name === 'DOW' + d);\n this.ids.days[d].date = state && state.id;\n }\n state = this.channelInfo.states.find(state => state.id && state.name === 'ICON' + d);\n this.ids.days[d].icon = state && (state.id !== this.id) && state.id ;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'STATE' + d);\n this.ids.days[d].state = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'TEMP_MIN' + d);\n this.ids.days[d].temperatureMin = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'TEMP_MAX' + d);\n this.ids.days[d].temperatureMax = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'TEMP' + d);\n this.ids.days[d].temperature = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'HUMIDITY' + d);\n this.ids.days[d].humidity = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'HUMIDITY_MIN' + d);\n this.ids.days[d].humidityMin = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'HUMIDITY_MAX' + d);\n this.ids.days[d].humidityMax = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'WIND_SPEED' + d);\n this.ids.days[d].windSpeed = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'WIND_ICON' + d);\n this.ids.days[d].windIcon = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'WIND_DIRECTION' + d);\n this.ids.days[d].windDirection = state && state.id;\n\n if (!this.ids.days[d].windDirection) {\n state = this.channelInfo.states.find(state => state.id && state.name === 'WIND_DIRECTION_STR' + d);\n this.ids.days[d].windDirection = state && state.id;\n }\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'PRECIPITATION_CHANCE' + d);\n this.ids.days[d].precipitation = state && state.id;\n\n let isEmpty = true;\n for (const id in this.ids.days[d]) {\n if (this.ids.days[d].hasOwnProperty(id) && this.ids.days[d][id]) {\n isEmpty = false;\n break\n }\n }\n if (isEmpty) {\n this.ids.days[d] = null;\n }\n }\n\n let max = this.ids.days.length - 1;\n while (!this.ids.days[max] && max >= 0) {\n max--;\n }\n if (max < this.ids.days.length && !this.ids.days[max + 1]) {\n this.ids.days.splice(max + 1, this.ids.days.length - max - 1);\n }\n }\n\n this.width = 2;\n this.props.tile.setState({isPointer: false});\n this.props.tile.setState({state: true});\n this.key = 'smart-weather-' + this.id + '-';\n\n this.stateRx.showDialog = false; // support dialog in this tile used in generic class)\n\n this.componentReady();\n }\n\n applySettings(settings) {\n settings = settings || (this.state && this.state.settings);\n if (settings) {\n if (settings.tempID && (!this.subscribes || this.subscribes.indexOf(settings.tempID) === -1)) {\n this.subscribes = this.subscribes || [];\n this.subscribes.push(settings.tempID);\n }\n if (settings.humidityID && (!this.subscribes || this.subscribes.indexOf(settings.humidityID) === -1)) {\n this.subscribes = this.subscribes || [];\n this.subscribes.push(settings.humidityID);\n }\n }\n }\n\n onUpdateTimer() {\n this.collectTimer = null;\n if (this.collectState) {\n this.setState(this.collectState);\n this.collectState = null;\n }\n }\n\n updateState(id, state) {\n if (id === this.ids.current.temperature ||\n id === this.ids.current.humidity ||\n id === this.ids.current.windChill ||\n id === this.ids.current.windSpeed ||\n id === this.state.settings.tempID ||\n id === this.state.settings.humidityID ||\n id === this.ids.current.temperatureMin ||\n id === this.ids.current.temperatureMax ||\n id === this.ids.current.precipitation ||\n id === this.ids.current.pressure) {\n const val = Math.round(parseFloat(state.val));\n if (!isNaN(val)) {\n this.collectState = this.collectState || {};\n this.collectState[id] = val;\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n }\n } else\n if (id === this.ids.current.icon ||\n id === this.ids.current.state ||\n id === this.ids.current.windIcon) {\n this.collectState = this.collectState || {};\n this.collectState[id] = state.val || '';\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else\n if (this.ids.current.location && this.ids.current.location.indexOf(id) !== -1) {\n this.collectState = this.collectState || {};\n if (state.val && state.val.replace(/[,.-]/g, '').trim()) {\n this.collectState.location = state.val || '';\n }\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else if (id === this.ids.current.windDirection || id === this.ids.current.windDegrees) {\n let dir = '';\n if (state && state.val !== null && state.val !== '' && state.val !== undefined && (typeof state.val === 'number' || parseInt(state.val, 10).toString() === state.val.toString())) {\n dir = I18n.t('wind_' + Utils.getWindDirection(state.val)).replace('wind_', '');\n } else {\n dir = state.val;\n }\n\n this.collectState = this.collectState || {};\n this.collectState[id] = dir;\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else if (id === this.ids.current.date) {\n let date = '';\n\n if (state && state.val) {\n date = Utils.date2string(state.val) || '';\n }\n\n this.collectState = this.collectState || {};\n this.collectState[id] = date;\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else {\n console.log(id + ' => ' + state.val);\n super.updateState(id, state);\n }\n }\n\n getDialogSettings() {\n const settings = super.getDialogSettings();\n settings.push({\n name: 'chartLast',\n value: this.state.settings.chartLast || false,\n type: 'boolean'\n });\n settings.push({\n name: 'tempID',\n value: this.state.settings.tempID || '',\n type: 'string'\n });\n settings.push({\n name: 'humidityID',\n value: this.state.settings.humidityID || '',\n type: 'string'\n });\n settings.push({\n name: 'locationText',\n value: this.state.settings.locationText || '',\n type: 'string'\n });\n settings.push({\n name: 'hideFirstDay',\n value: this.state.settings.hideFirstDay || false,\n type: 'boolean'\n });\n // remove doubleSize from list\n settings.forEach((item, i) => {\n if (item.name === 'doubleSize') {\n settings.splice(i, 1);\n return false\n }\n });\n return settings;\n }\n\n getCurrentIconDiv() {\n const classes = this.props.classes;\n let temp;\n temp = this.state.settings.tempID && this.state[this.state.settings.tempID];\n if (!temp && temp !== 0) {\n temp = this.ids.current.temperature && this.state[this.ids.current.temperature];\n }\n return (\n
\n {temp !== null && temp !== undefined ? (
{temp}°
) : null}\n
);\n }\n\n getCurrentDateLocationDiv() {\n const classes = this.props.classes;\n let date = this.ids.current.date && this.state[this.ids.current.date];\n let location = this.state.settings.locationText;\n location = location || this.state.location;\n\n location = location || I18n.t('Weather');\n date = date || Utils.date2string(new Date());\n\n return (\n
{date},
\n
{location}
\n
);\n }\n\n getTodayWindDiv() {\n const classes = this.props.classes;\n let windChill = this.ids.current.windchill && this.state[this.ids.current.date];\n let windDir = this.ids.current.windDirection && this.state[this.ids.current.windDirection];\n if (windDir !== null && windDir !== undefined && (typeof windDir === 'number' || parseInt(windDir, 10) === windDir)) {\n windDir = I18n.t('wind_' + Utils.getWindDirection(windDir)).replace('wind_', '');\n }\n let windSpeed = this.ids.current.windSpeed && this.state[this.ids.current.windSpeed];\n let windIcon = this.ids.current.windIcon && this.state[this.ids.current.windIcon];\n\n let state = this.ids.current.state && this.state[this.ids.current.state];\n return (\n\n {windChill !== null && windChill !== undefined ?\n (
\n {I18n.t('Windchill')}: \n {windChill} \n
)\n : null}\n\n {(windDir !== null && windDir !== undefined) || (windSpeed !== null && windSpeed !== undefined) ?\n (
\n {!windIcon ? (
{I18n.t('Wind')}: ) : null}\n {windIcon ? (
) : null}\n {windDir ? (
{windDir} ) : null}\n {windSpeed !== null && windSpeed !== undefined && !isNaN(windSpeed) ? (
{windSpeed}{this.windUnit} ) : null}\n
)\n : null}\n\n {state ? (
{state}
) : null}\n
);\n }\n\n getTodayTempDiv() {\n const classes = this.props.classes;\n let tempMin = this.ids.current.temperatureMin && this.state[this.ids.current.temperatureMin];\n let tempMax = this.ids.current.temperatureMax && this.state[this.ids.current.temperatureMax];\n let precipitation = this.ids.current.precipitation && this.state[this.ids.current.precipitation];\n\n let temp;\n if (tempMin !== null && tempMin !== undefined &&\n tempMax !== null && tempMax !== undefined && tempMin !== tempMax) {\n temp = [\n ({tempMax}° ),\n ( / ),\n ({tempMin}° )\n ];\n } else if (\n (tempMin !== null && tempMin !== undefined) ||\n (tempMax !== null && tempMax !== undefined)) {\n if (tempMin === null || tempMin === undefined) {\n tempMin = tempMax;\n }\n temp = ({tempMin}° );\n }\n\n if (!temp && !precipitation && precipitation !== 0) {\n return null;\n }\n\n return (\n {temp !== null && temp !== undefined ?\n (
\n {temp} \n
)\n : null}\n\n {precipitation !== null && precipitation !== undefined ?\n (
\n {I18n.t('Precip.')}: \n {precipitation}% \n
)\n : null}\n
);\n }\n\n render() {\n return this.wrapContent([\n this.getCurrentIconDiv(),\n this.getCurrentDateLocationDiv(),\n this.getTodayWindDiv(),\n this.getTodayTempDiv(),\n this.state.showDialog ?\n : null\n ]);\n }\n}\n\nexport default withStyles(styles)(SmartWeatherForecast);\n","/**\n * Copyright 2018 bluefox \n *\n * Licensed under the Creative Commons Attribution-NonCommercial License, Version 4.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n **/\nimport React from 'react';\nimport { withStyles } from '@material-ui/core/styles';\nimport PropTypes from 'prop-types';\nimport Paper from '@material-ui/core/Paper';\nimport Button from '@material-ui/core/Button';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogTitle from '@material-ui/core/DialogTitle';\n\nimport SmartDialogGeneric from './SmartDialogGeneric';\nimport I18n from '../i18n';\n\nconst HEIGHT_HEADER = 64;\nconst HEIGHT_ICON = 300;\nconst HEIGHT_TITLE = 48;\nconst HEIGHT_DATE = 48;\nconst HEIGHT_INFO = 48;\nconst HEIGHT_DESC = 120;\n\nconst styles = {\n 'header-div': {\n width: 'calc(100% - 1em)',\n position: 'relative',\n marginBottom: 16\n },\n 'date-div': {\n width: 'calc(100% - 1em)',\n position: 'relative',\n marginBottom: 16\n },\n 'date-start': {\n },\n 'date-end': {\n },\n 'title-div': {\n width: 'calc(100% - 1em)',\n position: 'relative',\n fontWeight: 'bold',\n fontSize: 20,\n marginBottom: 16\n },\n 'title-text': {\n },\n 'info-div': {\n width: 'calc(100% - 1em)',\n position: 'relative',\n marginBottom: 16\n },\n 'info-text': {\n },\n 'icon-div': {\n width: 'calc(100% - 1em)',\n position: 'relative',\n marginBottom: 16,\n cursor: 'pointer',\n backgroundSize: 'contain',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'center center',\n height: HEIGHT_ICON\n },\n 'desc-div': {\n width: 'calc(100% - 1em)',\n position: 'relative',\n marginBottom: 16\n },\n 'desc-text': {\n },\n 'chart-dialog': {\n zIndex: 2101\n },\n 'chart-dialog-paper': {\n width: 'calc(100% - 2em)',\n maxWidth: 'calc(100% - 2em)',\n height: 'calc(100% - 2em)',\n maxHeight: 'calc(100% - 2em)'\n },\n 'chart-dialog-img': {\n width: '100%',\n },\n 'chart-dialog-content': {\n width: 'calc(100% - 4em)',\n height: 'calc(100% - 4em)',\n cursor: 'pointer',\n backgroundSize: 'contain',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'center center',\n },\n\n};\n\nclass SmartDialogWarning extends SmartDialogGeneric {\n // expected:\n static propTypes = {\n name: PropTypes.oneOfType([\n PropTypes.string,\n PropTypes.object\n ]),\n dialogKey: PropTypes.string.isRequired,\n windowWidth: PropTypes.number,\n onClose: PropTypes.func.isRequired,\n objects: PropTypes.object,\n states: PropTypes.object,\n onCollectIds: PropTypes.func,\n enumNames: PropTypes.array,\n ids: PropTypes.object.isRequired,\n settings: PropTypes.object\n };\n\n constructor(props) {\n super(props);\n\n this.ids = this.props.ids;\n\n for (const id in this.ids) {\n if (this.ids.hasOwnProperty(id) && this.ids[id]) {\n this.subscribes = this.subscribes || [];\n if (this.ids[id] instanceof Array) {\n this.ids[id].forEach(i => this.subscribes.push(i));\n } else {\n this.subscribes.push(this.ids[id]);\n }\n }\n }\n\n this.stateRx.chartOpened = false;\n\n this.setMaxHeight();\n this.dialogStyle = {\n overflowY: 'auto'\n };\n\n const enums = [];\n this.props.enumNames.forEach(e => (enums.indexOf(e) === -1) && enums.push(e));\n if (enums.indexOf(this.props.name) === -1) {\n enums.push(this.props.name);\n }\n this.name = enums.join(' / ');\n this.collectState = null;\n this.collectTimer = null;\n\n this.componentReady();\n }\n\n setMaxHeight(states) {\n let maxHeight = 0;\n states = states || this.state;\n\n this.divs = {\n 'header': {height: HEIGHT_HEADER, visible: true},\n 'icon': {height: HEIGHT_ICON, visible: true},\n 'date': {height: HEIGHT_DATE, visible: true},\n 'title': {height: HEIGHT_TITLE, visible: states && states[this.ids.title]},\n 'info': {height: HEIGHT_INFO, visible: states && states[this.ids.info]},\n 'desc': {height: HEIGHT_DESC, visible: states && states[this.ids.description]}\n };\n\n // calculate height\n for (const name in this.divs) {\n if (this.divs.hasOwnProperty(name) && this.divs[name].visible) {\n maxHeight += this.divs[name].height + 16;\n }\n }\n\n if (this.dialogStyle.maxHeight !== maxHeight) {\n this.dialogStyle = {maxHeight: maxHeight};\n }\n }\n\n onUpdateTimer() {\n this.collectTimer = null;\n if (this.collectState) {\n this.setMaxHeight(this.collectState);\n this.setState(this.collectState);\n this.collectState = null;\n }\n }\n\n updateState(id, state) {\n if (id === this.ids.title ||\n id === this.ids.info ||\n id === this.ids.start ||\n id === this.ids.description ||\n id === this.ids.end ||\n id === this.ids.icon) {\n this.collectState = this.collectState || {};\n this.collectState[id] = state.val || '';\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else\n if (id === this.ids.warning) {\n const val = parseInt(state.val, 10) || 0;\n this.collectState = this.collectState || {};\n this.collectState[id] = val || '';\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else {\n console.log(id + ' => ' + state.val);\n super.updateState(id, state);\n }\n }\n\n getHeaderDiv() {\n if (!this.divs.header.visible) return null;\n return ({this.name}
);\n }\n\n onOpenNewWindow() {\n if (this.ids.icon && this.state[this.ids.icon]) {\n const win = window.open(this.state[this.ids.icon], '_blank');\n win.focus();\n }\n }\n\n getIconDiv() {\n if (!this.ids.icon || !this.state[this.ids.icon]) return null;\n const chart = this.state[this.ids.icon];\n\n return [( this.setState({chartOpened: true})}> ),\n this.state.chartOpened ? ( this.setState({chartOpened: false})}\n className={this.props.classes['chart-dialog']}\n aria-labelledby=\"alert-dialog-title\"\n aria-describedby=\"alert-dialog-description\"\n >\n {I18n.t('Chart')} \n this.onOpenNewWindow()}\n style={{\n backgroundImage: 'url(' + chart + ')'\n }}\n >\n \n \n this.setState({chartOpened: false})} color=\"primary\" autoFocus>{I18n.t('Close')} \n \n ) : null];\n }\n\n getDateDiv() {\n const classes = this.props.classes;\n let start = this.ids.start && this.state[this.ids.start];\n let end = this.ids.end && this.state[this.ids.end];\n\n return (\n
{start + (end ? ' - ' + end : '')}
\n
);\n }\n\n getTitleDiv() {\n const classes = this.props.classes;\n let title = this.ids.title && this.state[this.ids.title];\n\n return ();\n }\n\n getInfoDiv() {\n const classes = this.props.classes;\n let info = this.ids.info && this.state[this.ids.info];\n\n return ();\n }\n\n getDescDiv() {\n const classes = this.props.classes;\n let info = this.ids.description && this.state[this.ids.description];\n\n return ();\n }\n\n generateContent() {\n return [\n this.getHeaderDiv(),\n this.getTitleDiv(),\n this.getDateDiv(),\n this.getInfoDiv(),\n this.getIconDiv(),\n this.getDescDiv()\n ];\n }\n}\n\nexport default withStyles(styles)(SmartDialogWarning);","/**\n * Copyright 2018 bluefox \n *\n * Licensed under the Creative Commons Attribution-NonCommercial License, Version 4.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n **/\nimport React from 'react';\nimport { withStyles } from '@material-ui/core/styles';\nimport PropTypes from 'prop-types';\n\nimport SmartGeneric from './SmartGeneric';\nimport Dialog from '../Dialogs/SmartDialogWarning';\n\nconst styles = {\n 'icon-div': {\n position: 'absolute',\n width: 90,\n height: 90,\n zIndex: 0,\n left: 10,\n top: 24,\n backgroundSize: 'contain',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'center center'\n },\n 'date-div': {\n position: 'absolute',\n zIndex: 1,\n width: 'calc(100% - 2rem - 90px)',\n top: 16,\n right: 16\n },\n 'date-start': {\n fontWeight: 'normal',\n textAlign: 'left',\n width: '100%'\n },\n 'date-end': {\n textAlign: 'right',\n fontWeight: 'normal',\n width: '100%'\n },\n 'title-div': {\n position: 'absolute',\n zIndex: 1,\n fontWeight: 'bold',\n top: 55,\n width: 'calc(100% - 2rem - 90px)',\n right: 16,\n textAlign: 'left'\n },\n 'title-text': {\n },\n 'info-div': {\n position: 'absolute',\n zIndex: 1,\n maxWidth: 'calc(100% - 2rem - 90px)',\n fontWeight: 'normal',\n top: 75,\n right: 16,\n textAlign: 'left'\n },\n 'info-text': {\n overflow: 'hidden',\n fontSize: 14\n }\n};\n\nclass SmartWeatherForecast extends SmartGeneric {\n static propTypes = {\n classes: PropTypes.object.isRequired\n };\n\n constructor(props) {\n super(props);\n this.ids = {\n warning: null,\n title: null,\n info: null,\n start: null,\n end: null,\n icon: null,\n description: null\n };\n\n this.collectState = null;\n this.collectTimer = null;\n\n if (this.channelInfo.states) {\n // Actual\n let state = this.channelInfo.states.find(state => state.id && state.name === 'LEVEL');\n if (state) {\n this.id = state.id;\n this.ids.warning = state.id;\n } else {\n this.id = '';\n }\n state = this.channelInfo.states.find(state => state.id && state.name === 'TITLE');\n this.ids.title = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'INFO');\n this.ids.info = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'START');\n this.ids.start = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'END');\n this.ids.end = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'ICON');\n this.ids.icon = state && state.id;\n\n state = this.channelInfo.states.find(state => state.id && state.name === 'DESC');\n this.ids.description = state && state.id;\n }\n\n this.width = 2;\n this.props.tile.setState({isPointer: false});\n this.props.tile.setState({state: true});\n this.key = 'smart-warning-' + this.id + '-';\n\n this.stateRx.showDialog = false; // support dialog in this tile used in generic class)\n\n this.componentReady();\n }\n\n onUpdateTimer() {\n this.collectTimer = null;\n if (this.collectState) {\n if (this.collectState[this.id] !== undefined) {\n this.props.tile.setVisibility(!!this.collectState[this.id]);\n }\n this.setState(this.collectState);\n this.collectState = null;\n }\n }\n\n updateState(id, state) {\n if (id === this.ids.title ||\n id === this.ids.info ||\n id === this.ids.icon) {\n this.collectState = this.collectState || {};\n this.collectState[id] = state.val || '';\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else\n if (id === this.ids.start ||\n id === this.ids.end) {\n this.collectState = this.collectState || {};\n if (typeof state.val === 'string') {\n const year = new Date().getFullYear();\n state.val = state.val.replace(year.toString(), '').replace((year + 1).toString(), '')\n }\n\n this.collectState[id] = state.val || '';\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else\n if (id === this.ids.warning) {\n const val = parseInt(state.val, 10) || 0;\n this.collectState = this.collectState || {};\n this.collectState[id] = val || '';\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else {\n console.log(id + ' => ' + state.val);\n super.updateState(id, state);\n }\n }\n\n getDialogSettings() {\n const settings = super.getDialogSettings();\n // remove doubleSize from list\n settings.forEach((item, i) => {\n if (item.name === 'doubleSize') {\n settings.splice(i, 1);\n return false\n }\n });\n return settings;\n }\n\n getIconDiv() {\n const classes = this.props.classes;\n if (!this.ids.icon || !this.state[this.ids.icon]) return null;\n\n return (
);\n }\n\n getDateDiv() {\n const classes = this.props.classes;\n let start = this.ids.start && this.state[this.ids.start];\n let end = this.ids.end && this.state[this.ids.end];\n\n return (\n
{start}
\n {end ? (
-{end}
) : null}\n
);\n }\n\n getTitleDiv() {\n const classes = this.props.classes;\n let title = this.ids.title && this.state[this.ids.title];\n\n return ();\n }\n\n getInfoDiv() {\n const classes = this.props.classes;\n let info = this.ids.info && this.state[this.ids.info];\n\n return ();\n }\n\n render() {\n return this.wrapContent([\n this.getIconDiv(),\n this.getDateDiv(),\n this.getTitleDiv(),\n this.getInfoDiv(),\n this.state.showDialog ?\n : null\n ]);\n }\n}\n\nexport default withStyles(styles)(SmartWeatherForecast);\n","/**\n * Copyright 2018 bluefox \n *\n * Licensed under the Creative Commons Attribution-NonCommercial License, Version 4.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n **/\nimport React from 'react';\nimport { withStyles } from '@material-ui/core/styles';\nimport PropTypes from 'prop-types';\nimport Paper from '@material-ui/core/Paper';\n\nimport SmartDialogGeneric from './SmartDialogGeneric';\n\nconst HEIGHT_TITLE = 64;\nconst HEIGHT_IFRAME = 300;\n\nconst styles = {\n 'header-div': {\n width: 'calc(100% - 1em)',\n position: 'relative',\n marginBottom: 16\n },\n 'title-div': {\n width: 'calc(100% - 1em)',\n position: 'relative',\n fontWeight: 'bold',\n fontSize: 20,\n marginBottom: 16,\n },\n 'title-text': {\n color: 'black'\n },\n\n 'iframe-div': {\n width: 'calc(100% - 1em)',\n minHeight: HEIGHT_IFRAME,\n height: 'calc(100% - ' + HEIGHT_TITLE + 'px)'\n },\n iframe: {\n width: '100%',\n height: '100%',\n border: 0\n },\n 'image-img': {\n width: '100%',\n height: '100%',\n objectFit: 'contain'\n },\n\n};\n\nclass SmartDialogURL extends SmartDialogGeneric {\n // expected:\n static propTypes = {\n name: PropTypes.oneOfType([\n PropTypes.string,\n PropTypes.object\n ]),\n dialogKey: PropTypes.string.isRequired,\n windowWidth: PropTypes.number,\n onClose: PropTypes.func.isRequired,\n objects: PropTypes.object,\n states: PropTypes.object,\n onCollectIds: PropTypes.func,\n settings: PropTypes.object,\n image: PropTypes.bool\n };\n\n constructor(props) {\n super(props);\n\n this.ids = this.props.ids;\n\n this.setMaxHeight();\n this.dialogStyle = {overflowY: 'auto'};\n this.stateRx.url = this.getUrl(this.props.settings.background);\n\n if (this.props.settings.fullWidth) {\n this.dialogStyle = {width: 'calc(100% - 4rem)', maxWidth: 'calc(100% - 4rem)', left: '2rem'};\n }\n\n this.componentReady();\n }\n\n getUrl(url) {\n let _url;\n if (this.props.image && url) {\n if (url.indexOf('?') !== -1) {\n _url = url + '&ts=' + Date.now();\n } else {\n _url = url + '?ts=' + Date.now();\n }\n return _url;\n } else {\n return url;\n }\n }\n\n setMaxHeight(states) {\n let maxHeight = 0;\n states = states || this.state;\n\n this.divs = {\n 'title': {height: HEIGHT_TITLE, visible: !!this.props.settings.title},\n 'iframe': {height: HEIGHT_IFRAME, visible: true}\n };\n\n // calculate height\n for (const name in this.divs) {\n if (this.divs.hasOwnProperty(name) && this.divs[name].visible) {\n maxHeight += this.divs[name].height + 16;\n }\n }\n\n if (this.dialogStyle.maxHeight !== maxHeight) {\n this.dialogStyle = {maxHeight: maxHeight};\n }\n }\n\n updateUrl() {\n if (this.props.image) {\n this.setState({url: this.getUrl(this.props.settings.background)});\n }\n }\n\n componentDidMount () {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n if (this.props.settings.updateInDialog || this.props.settings.update) {\n this.interval = setInterval(() => this.updateUrl(), this.props.settings.updateInDialog || this.props.settings.update);\n }\n }\n\n componentWillUnmount() {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n }\n\n onOpenNewWindow() {\n if (this.props.settings.background) {\n const win = window.open(this.props.settings.background, '_blank');\n win.focus();\n }\n }\n\n getTitleDiv() {\n const classes = this.props.classes;\n let title = this.props.settings.title;\n\n if (title) {\n return ();\n } else {\n return null;\n }\n }\n\n getIFrameDiv() {\n const style = {height: 'calc(100% - ' + (this.props.settings.title ? HEIGHT_TITLE : '0') + 'px)'};\n if (!this.props.image && this.state.url) {\n return ( this.onOpenNewWindow()} className={this.props.classes['iframe-div']} style={style}>\n );\n } else if (this.state.url) {\n return ( this.onOpenNewWindow()} className={this.props.classes['iframe-div']}>\n \n );\n }\n }\n\n generateContent() {\n return [\n this.getTitleDiv(),\n this.getIFrameDiv()\n ];\n }\n}\n\nexport default withStyles(styles)(SmartDialogURL);","/**\n * Copyright 2018 bluefox \n *\n * Licensed under the Creative Commons Attribution-NonCommercial License, Version 4.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n **/\nimport React from 'react';\nimport { withStyles } from '@material-ui/core/styles';\nimport PropTypes from 'prop-types';\n\nimport {FaVideo as IconCam} from 'react-icons/fa';\nimport SmartGeneric from './SmartGeneric';\nimport Dialog from '../Dialogs/SmartDialogURL';\nimport Theme from '../theme';\nimport Utils from '../Utils';\nimport Icon from \"../icons/Thermometer\";\n\nconst styles = {\n 'title-div': {\n position: 'absolute',\n zIndex: 1,\n fontWeight: 'bold',\n bottom: 0,\n left: 0,\n height: 48,\n background: 'rgba(255,255,255,0.45)',\n color: 'rgba(0, 0, 0, 0.6)',\n width: '100%',\n textAlign: 'left'\n },\n 'title-text': {\n paddingLeft: 16,\n paddingTop: 16,\n },\n iframe: {\n position: 'absolute',\n zIndex: 0,\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n border: 0\n }\n};\n\nclass SmartURL extends SmartGeneric {\n static propTypes = {\n classes: PropTypes.object.isRequired\n };\n\n constructor(props) {\n super(props);\n this.ids = {\n url: null,\n };\n\n this.collectState = null;\n this.collectTimer = null;\n\n if (this.channelInfo.states) {\n // Actual\n let state = this.channelInfo.states.find(state => state.id && state.name === 'URL');\n if (state) {\n this.id = state.id;\n this.ids.url = state.id;\n const settingsId = state.settingsId;\n if (settingsId) {\n const settings = Utils.getSettingsCustomURLs(this.props.objects[settingsId], null, {user: this.props.user});\n if (settings) {\n const tile = settings.find(e => e.id === state.id);\n if (tile) {\n this.stateRx.settings = JSON.parse(JSON.stringify(tile));\n this.customSettings = this.stateRx.settings;\n }\n }\n }\n } else {\n this.id = '';\n }\n }\n\n if (this.id) {\n const m = this.id.match(/^_custom_(\\d+)$/);\n if (m && Date.now() - m[1] < 500) {\n this.stateRx.showSettings = true;\n }\n }\n\n this.image = this.isImage();\n if (this.image) {\n this.props.tile.setBackgroundImage(this.customSettings.background || '', true);\n }\n\n this.width = 2;\n this.props.tile.setState({isPointer: false});\n this.props.tile.setState({state: true});\n this.key = 'smart-warning-' + this.id + '-';\n this.props.tile.setVisibility(this.id && !!this.stateRx.settings);\n this.stateRx.showDialog = false; // support dialog in this tile used in generic class)\n\n this.interval = null;\n\n this.componentReady();\n }\n\n isImage() {\n return this.customSettings.isImage || (this.customSettings.background && this.customSettings.background.toLowerCase().match(/\\.png|\\.jpg|\\.gif|\\.jpeg/));\n }\n\n componentWillUnmount() {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n }\n\n updateUrl() {\n if (!this.customSettings.background) return;\n\n if (this.image) {\n if (this.customSettings.background.indexOf('?') !== -1) {\n this.props.tile.setBackgroundImage(this.customSettings.background + '&ts=' + Date.now(), true, true);\n } else {\n this.props.tile.setBackgroundImage(this.customSettings.background + '?ts=' + Date.now(), true, true);\n }\n }\n }\n\n componentDidMount () {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n if (this.customSettings && this.customSettings.update) {\n this.interval = setInterval(() => this.updateUrl(), this.customSettings.update);\n }\n }\n\n onUpdateTimer() {\n this.collectTimer = null;\n if (this.collectState) {\n if (this.collectState[this.id] !== undefined) {\n this.props.tile.setVisibility(!!this.collectState[this.id]);\n }\n this.setState(this.collectState);\n this.collectState = null;\n }\n }\n\n updateState(id, state) {\n if (id === this.ids.url) {\n this.collectState = this.collectState || {};\n this.collectState[id] = state.val || '';\n this.collectTimer && clearTimeout(this.collectTimer);\n this.collectTimer = setTimeout(() => this.onUpdateTimer(), 200);\n } else {\n console.log(id + ' => ' + state.val);\n super.updateState(id, state);\n }\n }\n\n getDialogSettings() {\n const settings = super.getDialogSettings();\n\n settings.unshift({\n name: 'hideIcon',\n value: this.state.settings.hideIcon || false,\n type: 'boolean'\n });\n\n settings.unshift({\n name: 'fullWidth',\n value: this.state.settings.fullWidth || false,\n type: 'boolean'\n });\n\n settings.unshift({\n name: 'updateInDialog',\n value: this.state.settings.updateInDialog || 0,\n type: 'number'\n });\n\n settings.unshift({\n name: 'update',\n value: this.state.settings.update || 0,\n type: 'number'\n });\n\n settings.unshift({\n name: 'title',\n value: (this.state.settings.title === undefined) ? '' : this.state.settings.title,\n type: 'text'\n });\n\n settings.unshift({\n name: 'isImage',\n value: this.state.settings.isImage || false,\n type: 'boolean'\n });\n\n settings.forEach((e, i) => {\n if (e.name === 'background') {\n e.type = 'text';\n settings.splice(i, 1);\n settings.unshift(e);\n return false;\n }\n });\n\n // remove name from list\n settings.forEach((e, i) => {\n if (e.name === 'name') {\n settings.splice(i, 1);\n return false;\n }\n });\n\n\n settings.unshift({\n type: 'delete'\n });\n return settings;\n }\n\n saveDialogSettings(settings) {\n if (settings) {\n settings.update = parseInt(settings.update, 10) || 0;\n if (settings.update && settings.update < 500) {\n settings.update = 500;\n }\n settings.updateInDialog = parseInt(settings.updateInDialog, 10) || 0;\n if (settings.updateInDialog && settings.updateInDialog < 500) {\n settings.updateInDialog = 500;\n }\n }\n\n super.saveDialogSettings(settings, function (newSettings) {\n this.customSettings = newSettings;\n\n this.componentDidMount();\n this.image = this.isImage();\n if (this.image && newSettings) {\n this.props.tile.setBackgroundImage((this.state.settings && this.state.settings.background) || '', true);\n }\n this.setState({settings: newSettings});\n }.bind(this));\n }\n\n getIconDiv() {\n let customIcon;\n if (this.state.settings.hideIcon) return null;\n\n if (this.state.settings.icon) {\n customIcon = ( );\n } else {\n customIcon = ( );\n }\n return (\n \n {customIcon}\n
\n );\n }\n\n getIFrameDiv() {\n if (!this.image && this.state.settings.background) {\n return (