github.com/stampzilla/stampzilla-go@v2.0.0-rc9+incompatible/nodes/stampzilla-server/web/src/routes/automation/components/Scene.js (about) 1 import React from 'react'; 2 import { connect } from 'react-redux'; 3 import diff from 'immutable-diff'; 4 import classnames from 'classnames'; 5 6 import './Scene.scss'; 7 import Trait from '../../dashboard/Trait'; 8 import { traitPriority, traitNames, traitStates } from '../../dashboard/Device'; 9 10 // List of traits that is used to filter out useable devices 11 const traits = ['OnOff', 'Brightness', 'ColorSetting']; 12 13 const checkBox = ({ 14 device, trait, checked, onChange, 15 }) => ( 16 <div className="checkbox custom-control custom-checkbox "> 17 <input 18 type="checkbox" 19 className="custom-control-input" 20 id={`${device.get('id')}${trait}`} 21 checked={checked || false} 22 onChange={event => onChange(event.target.checked)} 23 /> 24 <label 25 className="custom-control-label" 26 htmlFor={`${device.get('id')}${trait}`} 27 /> 28 </div> 29 ); 30 31 class Scene extends React.Component { 32 constructor(props) { 33 super(props); 34 35 this.state = { 36 recording: false, 37 }; 38 } 39 40 componentWillReceiveProps(props) { 41 const { devices } = this.props; 42 43 const { recording } = this.state; 44 const states = (this.props && this.props.states) || {}; 45 46 const useableDevices = devices.reduce((acc, dev) => { 47 if ( 48 traits.find(trait => (dev.get('traits') || []).indexOf(trait) !== -1) 49 ) { 50 acc.push(dev.get('id')); 51 } 52 return acc; 53 }, []); 54 55 if (recording) { 56 const changes = diff(props.devices, this.props.devices); 57 58 changes.forEach((change) => { 59 const [device, type, key] = change.get('path').toJS(); 60 if (type !== 'state') { 61 return; // No state change 62 } 63 if (useableDevices.indexOf(device) === -1) { 64 return; // Not a useable device 65 } 66 67 states[device] = { 68 ...states[device], 69 [key]: props.devices.getIn(change.get('path')), 70 }; 71 }); 72 73 if (states !== this.props.states) { 74 this.onStateChange(states); 75 } 76 } 77 } 78 79 onStateChange = (states) => { 80 const { onChange } = this.props; 81 if (onChange) { 82 onChange(states); 83 } 84 }; 85 86 onToggleDevice = device => (checked) => { 87 const states = (this.props && this.props.states) || {}; 88 if (checked) { 89 states[device.get('id')] = states[device.get('id')] || {}; 90 } else { 91 delete states[device.get('id')]; 92 } 93 this.onStateChange(states); 94 }; 95 onToggleTrait = (device, trait) => (checked) => { 96 const states = (this.props && this.props.states) || {}; 97 if (checked) { 98 states[device.get('id')] = states[device.get('id')] || {}; 99 states[device.get('id')][traitStates[trait]] = device.getIn([ 100 'state', 101 traitStates[trait], 102 ]); 103 } else { 104 delete states[device.get('id')][traitStates[trait]]; 105 } 106 this.onStateChange(states); 107 }; 108 109 onChangeTrait = (device, trait) => (value) => { 110 const states = (this.props && this.props.states) || {}; 111 112 states[device.get('id')] = states[device.get('id')] || {}; 113 states[device.get('id')][traitStates[trait]] = value; 114 115 this.onStateChange(states); 116 }; 117 118 render() { 119 const { devices, states } = this.props; 120 const { recording } = this.state; 121 122 const useableDevices = devices 123 .reduce((acc, dev) => { 124 if ( 125 traits.find(trait => (dev.get('traits') || []).indexOf(trait) !== -1) 126 ) { 127 acc.push(dev); 128 } 129 return acc; 130 }, []) 131 .sort((a, b) => a.get('name').localeCompare(b.get('name'))); 132 133 return ( 134 <div className="saved-state-builder"> 135 <div className="menu mb-1"> 136 <button 137 className={recording ? 'btn btn-danger' : 'btn btn-secondary'} 138 onClick={() => this.setState({ recording: !recording })} 139 type="button" 140 > 141 {recording ? 'Recording' : 'Record'} 142 </button> 143 <button className="btn btn-secondary ml-1 hide" type="button"> 144 Select all lights 145 </button> 146 </div> 147 <div className="devices"> 148 {useableDevices.map((dev) => { 149 const sortedTraits = 150 dev.get('traits') && 151 dev.get('traits').sort((a, b) => { 152 const prioA = traitPriority.findIndex(trait => trait === a); 153 const prioB = traitPriority.findIndex(trait => trait === b); 154 return prioA - prioB; 155 }); 156 const selected = 157 states && Object.keys(states).indexOf(dev.get('id')) !== -1; 158 159 return ( 160 <div 161 key={dev.get('id')} 162 className={classnames({ 163 selected, 164 })} 165 > 166 <div className="d-flex mt-2"> 167 {checkBox({ 168 device: dev, 169 checked: selected || false, 170 onChange: this.onToggleDevice(dev), 171 })} 172 {dev.get('name')} 173 </div> 174 175 {sortedTraits && 176 sortedTraits.map(trait => ( 177 <div className="d-flex ml-3 trait" key={trait}> 178 {checkBox({ 179 device: dev, 180 trait: traitStates[trait], 181 checked: 182 states && 183 states[dev.get('id')] && 184 states[dev.get('id')][traitStates[trait]] !== 185 undefined, 186 onChange: this.onToggleTrait(dev, trait), 187 })} 188 <div className="mr-2">{traitNames[trait] || trait}</div> 189 <div className="flex-grow-1 d-flex align-items-center justify-content-end"> 190 <Trait 191 trait={trait} 192 device={dev} 193 state={ 194 states && 195 states[dev.get('id')] && 196 states[dev.get('id')][traitStates[trait]] !== 197 undefined 198 ? states[dev.get('id')][traitStates[trait]] 199 : traitStates[trait] && 200 dev.getIn(['state', traitStates[trait]]) 201 } 202 onChange={this.onChangeTrait(dev, trait)} 203 /> 204 </div> 205 </div> 206 ))} 207 </div> 208 ); 209 })} 210 </div> 211 </div> 212 ); 213 } 214 } 215 216 const mapStateToProps = state => ({ 217 devices: state.getIn(['devices', 'list']), 218 }); 219 220 export default connect(mapStateToProps)(Scene);