diff --git a/static/fluent/en-CA/main.ftl b/static/fluent/en-CA/main.ftl index 978eeed9c..a2c61bb8e 100644 --- a/static/fluent/en-CA/main.ftl +++ b/static/fluent/en-CA/main.ftl @@ -270,6 +270,7 @@ SmartPlug = Smart Plug Light = Light DoorSensor = Door Sensor MotionSensor = Motion Sensor +OccupancySensor = Occupancy Sensor LeakSensor = Leak Sensor PushButton = Push Button VideoCamera = Video Camera @@ -306,6 +307,8 @@ color-temperature = Colour Temperature video-unsupported = Sorry, video is not supported in your browser. motion = Motion no-motion = No Motion +occupied = Occupied +unoccupied = Unoccupied open = Open closed = Closed locked = Locked diff --git a/static/fluent/en-GB/main.ftl b/static/fluent/en-GB/main.ftl index ac4136c58..f0a1bf430 100644 --- a/static/fluent/en-GB/main.ftl +++ b/static/fluent/en-GB/main.ftl @@ -270,6 +270,7 @@ SmartPlug = Smart Plug Light = Light DoorSensor = Door Sensor MotionSensor = Motion Sensor +OccupancySensor = Occupancy Sensor LeakSensor = Leak Sensor PushButton = Push Button VideoCamera = Video Camera @@ -306,6 +307,8 @@ color-temperature = Colour Temperature video-unsupported = Sorry, video is not supported in your browser. motion = Motion no-motion = No Motion +occupied = Occupied +unoccupied = Unoccupied open = Open closed = Closed locked = Locked diff --git a/static/fluent/en-US/main.ftl b/static/fluent/en-US/main.ftl index daa3944a3..697d96f7c 100644 --- a/static/fluent/en-US/main.ftl +++ b/static/fluent/en-US/main.ftl @@ -271,6 +271,7 @@ SmartPlug = Smart Plug Light = Light DoorSensor = Door Sensor MotionSensor = Motion Sensor +OccupancySensor = Occupancy Sensor LeakSensor = Leak Sensor PushButton = Push Button VideoCamera = Video Camera @@ -307,6 +308,8 @@ color-temperature = Color Temperature video-unsupported = Sorry, video is not supported in your browser. motion = Motion no-motion = No Motion +occupied = Occupied +unoccupied = Unoccupied open = Open closed = Closed locked = Locked diff --git a/static/images/component-icons/occupancy-sensor-occupied.svg b/static/images/component-icons/occupancy-sensor-occupied.svg new file mode 100644 index 000000000..6b08a5e05 --- /dev/null +++ b/static/images/component-icons/occupancy-sensor-occupied.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + diff --git a/static/images/component-icons/occupancy-sensor-unoccupied.svg b/static/images/component-icons/occupancy-sensor-unoccupied.svg new file mode 100644 index 000000000..b48af1066 --- /dev/null +++ b/static/images/component-icons/occupancy-sensor-unoccupied.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/static/images/thing-icons/occupancy_sensor.svg b/static/images/thing-icons/occupancy_sensor.svg new file mode 100644 index 000000000..001be2a3f --- /dev/null +++ b/static/images/thing-icons/occupancy_sensor.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/static/js/app.js b/static/js/app.js index d2bbbf909..666710fa8 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -518,6 +518,7 @@ require('./components/capability/lock'); require('./components/capability/motion-sensor'); require('./components/capability/multi-level-sensor'); require('./components/capability/multi-level-switch'); +require('./components/capability/occupancy-sensor'); require('./components/capability/on-off-switch'); require('./components/capability/push-button'); require('./components/capability/smart-plug'); @@ -546,6 +547,7 @@ require('./components/property/locked'); require('./components/property/motion'); require('./components/property/number'); require('./components/property/numeric-label'); +require('./components/property/occupied'); require('./components/property/on-off'); require('./components/property/open'); require('./components/property/pushed'); diff --git a/static/js/components/capability/occupancy-sensor.js b/static/js/components/capability/occupancy-sensor.js new file mode 100644 index 000000000..377dd20c7 --- /dev/null +++ b/static/js/components/capability/occupancy-sensor.js @@ -0,0 +1,98 @@ +/** + * OccupancySensorCapability + * + * A bubble showing an occupancy sensor icon. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +'use strict'; + +const BaseComponent = require('../base-component'); +const fluent = require('../../fluent'); + +const template = document.createElement('template'); +template.innerHTML = ` + +
+
--
+
+`; + +class OccupancySensorCapability extends BaseComponent { + constructor() { + super(template); + + this._icon = this.shadowRoot.querySelector('#icon'); + this._label = this.shadowRoot.querySelector('#label'); + + this._occupied = false; + } + + connectedCallback() { + this.occupied = typeof this.dataset.occupied !== 'undefined' ? this.dataset.occupied : null; + } + + get occupied() { + return this._occupied; + } + + set occupied(value) { + this._occupied = Boolean(value); + + if (value === null) { + this._icon.classList.remove('occupied'); + this._label.innerText = fluent.getMessage('ellipsis'); + } else if (this._occupied) { + this._icon.classList.add('occupied'); + this._label.innerText = fluent.getMessage('occupied'); + } else { + this._icon.classList.remove('occupied'); + this._label.innerText = fluent.getMessage('unoccupied'); + } + } +} + +window.customElements.define('webthing-occupancy-sensor-capability', OccupancySensorCapability); +module.exports = OccupancySensorCapability; diff --git a/static/js/components/property/occupied.js b/static/js/components/property/occupied.js new file mode 100644 index 000000000..72a968cc0 --- /dev/null +++ b/static/js/components/property/occupied.js @@ -0,0 +1,22 @@ +/** + * OccupiedProperty + * + * A bubble showing an occupied or not occupied label. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +'use strict'; + +const StringLabelProperty = require('./string-label'); + +class OccupiedProperty extends StringLabelProperty { + connectedCallback() { + this.uppercase = true; + super.connectedCallback(); + } +} + +window.customElements.define('webthing-occupied-property', OccupiedProperty); +module.exports = OccupiedProperty; diff --git a/static/js/icons.js b/static/js/icons.js index f7d6ecf8d..c1e896b71 100644 --- a/static/js/icons.js +++ b/static/js/icons.js @@ -46,6 +46,8 @@ function capabilityToIcon(capability) { return '/images/thing-icons/door_sensor.svg'; case 'MotionSensor': return '/images/thing-icons/motion_sensor.svg'; + case 'OccupancySensor': + return '/images/thing-icons/occupancy_sensor.svg'; case 'LeakSensor': return '/images/thing-icons/leak_sensor.svg'; case 'SmokeSensor': diff --git a/static/js/logs/log.js b/static/js/logs/log.js index 7401c05c2..516cfbfa0 100644 --- a/static/js/logs/log.js +++ b/static/js/logs/log.js @@ -863,6 +863,8 @@ class Log { return value ? fluent.getMessage('on') : fluent.getMessage('off'); case 'MotionProperty': return value ? fluent.getMessage('motion') : fluent.getMessage('no-motion'); + case 'OccupiedProperty': + return value ? fluent.getMessage('occupied') : fluent.getMessage('unoccupied'); case 'OpenProperty': return value ? fluent.getMessage('open') : fluent.getMessage('closed'); case 'LeakProperty': diff --git a/static/js/schema-impl/capability/capabilities.js b/static/js/schema-impl/capability/capabilities.js index 4b266cbf8..5e0af989d 100644 --- a/static/js/schema-impl/capability/capabilities.js +++ b/static/js/schema-impl/capability/capabilities.js @@ -22,6 +22,7 @@ const Lock = require('./lock'); const MotionSensor = require('./motion-sensor'); const MultiLevelSensor = require('./multi-level-sensor'); const MultiLevelSwitch = require('./multi-level-switch'); +const OccupancySensor = require('./occupancy-sensor'); const OnOffSwitch = require('./on-off-switch'); const PushButton = require('./push-button'); const SmartPlug = require('./smart-plug'); @@ -56,6 +57,8 @@ function createThingFromCapability(capability, thingModel, description, format) return new DoorSensor(thingModel, description, format); case 'MotionSensor': return new MotionSensor(thingModel, description, format); + case 'OccupancySensor': + return new OccupancySensor(thingModel, description, format); case 'LeakSensor': return new LeakSensor(thingModel, description, format); case 'SmokeSensor': @@ -110,6 +113,8 @@ function getClassFromCapability(capability) { return 'door-sensor'; case 'MotionSensor': return 'motion-sensor'; + case 'OccupancySensor': + return 'occupancy-sensor'; case 'LeakSensor': return 'leak-sensor'; case 'SmokeSensor': diff --git a/static/js/schema-impl/capability/occupancy-sensor.js b/static/js/schema-impl/capability/occupancy-sensor.js new file mode 100644 index 000000000..83b35e8a4 --- /dev/null +++ b/static/js/schema-impl/capability/occupancy-sensor.js @@ -0,0 +1,78 @@ +/** + * Occupancy Sensor. + * + * UI element representing an Occupancy Sensor. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +'use strict'; + +const Thing = require('./thing'); + +class OccupancySensor extends Thing { + /** + * OccupancySensor Constructor (extends Thing). + * + * @param {Object} description Thing description object. + * @param {Number} format See Constants.ThingFormat + */ + constructor(model, description, format) { + super(model, description, format, { + baseIcon: '/images/thing-icons/occupancy_sensor.svg', + }); + } + + /** + * Find any properties required for this view. + */ + findProperties() { + this.occupiedProperty = null; + + // Look for properties by type first. + for (const name in this.displayedProperties) { + const type = this.displayedProperties[name].property['@type']; + + if (type === 'OccupiedProperty') { + this.occupiedProperty = name; + break; + } + } + + // If necessary, match on name. + if (this.occupiedProperty === null && this.displayedProperties.occupied) { + this.occupiedProperty = 'occupied'; + } + } + + get icon() { + return this.element.querySelector('webthing-occupancy-sensor-capability'); + } + + /** + * Update the display for the provided property. + * @param {string} name - name of the property + * @param {*} value - value of the property + */ + updateProperty(name, value) { + value = super.updateProperty(name, value); + + if (!this.displayedProperties.hasOwnProperty(name)) { + return; + } + + if (name === this.occupiedProperty) { + this.icon.occupied = !!value; + } + } + + iconView() { + return ` + + `; + } +} + +module.exports = OccupancySensor; diff --git a/static/js/schema-impl/capability/thing.js b/static/js/schema-impl/capability/thing.js index ac0f79cb6..845f81311 100644 --- a/static/js/schema-impl/capability/thing.js +++ b/static/js/schema-impl/capability/thing.js @@ -33,6 +33,7 @@ const LevelDetail = require('../property/level'); const LockActionDetail = require('../action/lock'); const LockedDetail = require('../property/locked'); const MotionDetail = require('../property/motion'); +const OccupiedDetail = require('../property/occupied'); const NumberDetail = require('../property/number'); const OnOffDetail = require('../property/on-off'); const OpenDetail = require('../property/open'); @@ -218,6 +219,9 @@ class Thing { case 'MotionProperty': detail = new MotionDetail(this, name, convertedProperty); break; + case 'OccupiedProperty': + detail = new OccupiedDetail(this, name, convertedProperty); + break; case 'OpenProperty': detail = new OpenDetail(this, name, convertedProperty); break; diff --git a/static/js/schema-impl/property/occupied.js b/static/js/schema-impl/property/occupied.js new file mode 100644 index 000000000..e6b2469d7 --- /dev/null +++ b/static/js/schema-impl/property/occupied.js @@ -0,0 +1,45 @@ +/** + * OccupiedDetail + * + * A bubble showing occupied state. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +'use strict'; + +const StringLabelDetail = require('./string-label'); +const Utils = require('../../utils'); +const fluent = require('../../fluent'); + +class OccupiedDetail extends StringLabelDetail { + constructor(thing, name, property) { + super(thing, name, !!property.readOnly, property.title || fluent.getMessage('occupied')); + this.id = `occupied-${Utils.escapeHtmlForIdClass(this.name)}`; + } + + view() { + const readOnly = this.readOnly ? 'data-read-only="true"' : ''; + + return ` + + `; + } + + update(value) { + if (!this.label) { + return; + } + + this.labelElement.value = value + ? fluent.getMessage('occupied') + : fluent.getMessage('unoccupied'); + this.labelElement.inverted = value; + } +} + +module.exports = OccupiedDetail; diff --git a/static/js/utils.ts b/static/js/utils.ts index 10c62a673..26a98def6 100644 --- a/static/js/utils.ts +++ b/static/js/utils.ts @@ -197,6 +197,7 @@ export function sortCapabilities(capabilities: string[]): string[] { 'EnergyMonitor', 'DoorSensor', 'MotionSensor', + 'OccupancySensor', 'LeakSensor', 'SmokeSensor', 'PushButton', diff --git a/static/js/views/new-thing.js b/static/js/views/new-thing.js index fb0beac84..03a261ef7 100644 --- a/static/js/views/new-thing.js +++ b/static/js/views/new-thing.js @@ -317,6 +317,7 @@ class NewThing { 'light', 'door-sensor', 'motion-sensor', + 'occupancy-sensor', 'leak-sensor', 'smoke-sensor', 'push-button', diff --git a/static/js/views/new-web-thing.js b/static/js/views/new-web-thing.js index a9177d746..2f49a9838 100644 --- a/static/js/views/new-web-thing.js +++ b/static/js/views/new-web-thing.js @@ -126,6 +126,7 @@ class NewWebThing { 'light', 'door-sensor', 'motion-sensor', + 'occupancy-sensor', 'leak-sensor', 'smoke-sensor', 'push-button',