diff --git a/app/components/bridge-controls.js b/app/components/bridge-controls.js index bc7c7e9..2da3c39 100644 --- a/app/components/bridge-controls.js +++ b/app/components/bridge-controls.js @@ -5,9 +5,10 @@ export default Em.Component.extend({ bridgeUsername: null, + updateGroupsData: true, groupsData: null, lightsData: null, - activeLights: [], + activeLights: Em.A(), apiURL: function(){ return 'http://' + this.get('bridgeIp') + '/api/' + this.get('bridgeUsername'); @@ -15,6 +16,19 @@ export default Em.Component.extend({ init: function() { this._super(); + this.doUpdateGroupsData(); + + this.set('lightsDataIntervalHandle', setInterval(this.updateLightData.bind(this), 1000)); + }, + + onUpdateGroupsDataChange: function(){ + if(this.get('updateGroupsData')){ + var self = this; + setTimeout(function(){ self.doUpdateGroupsData(); }, 1000); + } + }.observes('updateGroupsData'), + + doUpdateGroupsData: function(){ var self = this; Em.$.get(this.get('apiURL') + '/groups', function (result, status) { @@ -23,10 +37,10 @@ export default Em.Component.extend({ } }); - this.set('lightsDataIntervalHandle', setInterval(this.updateLightData.bind(this), 1000)); + this.toggleProperty('updateGroupsData'); }, - tabList: ["Lights", "Scenes", "Party"], + tabList: ["Lights", "Scenes", "Music"], selectedTab: 0, tabData: function(){ var tabData = [], selectedTab = this.get('selectedTab'); @@ -46,7 +60,7 @@ export default Em.Component.extend({ lightsTabSelected: Em.computed.equal('selectedTab', 0), scenesTabSelected: Em.computed.equal('selectedTab', 1), - partyTabSelected: Em.computed.equal('selectedTab', 2), + musicTabSelected: Em.computed.equal('selectedTab', 2), actions: { changeTab: function(tabName){ @@ -70,15 +84,13 @@ export default Em.Component.extend({ } self.set('lightsData', result); - } else if(status !== 'success' ) { + } else if(status !== 'success') { // something went terribly wrong ( user got unauthenticated? ) and we'll need to start all over clearInterval(self.get('lightsDataIntervalHandle')); this.setProperties({ bridgeIp: null, bridgeUsername: null }); - - console.error(status + ': ' + result); } }); } diff --git a/app/components/controls/group-control.js b/app/components/controls/group-control.js index ef7b4bf..b6be584 100644 --- a/app/components/controls/group-control.js +++ b/app/components/controls/group-control.js @@ -5,42 +5,61 @@ export default Em.Component.extend({ tagName: null, - groupSelection: null, + groupIdSelection: '0', actions: { selectGroup: function(selection){ - this.set('groupSelection', selection); + this.set('groupIdSelection', selection); + }, + toggleConfirmDeleteGroupsModal: function(groupName, groupId){ + this.setProperties({ + deleteGroupName: groupName, + deleteGroupId: groupId + }); + this.toggleProperty('isShowingConfirmDeleteModal'); }, - toggleAddGroupsModal: function(){ this.toggleProperty('isShowingAddGroupsModal'); } }, groupsArrData: function(){ - var groupsData = this.get('groupsData'), lightsData = this.get('lightsData'), groupsArrData = [], ids = []; + var groupsData = this.get('groupsData'), lightsData = this.get('lightsData'), groupsArrData = [], ids = [], groupIdSelection = this.get('groupIdSelection'); for (let key in lightsData) { if(lightsData.hasOwnProperty(key) && lightsData[key].state.reachable){ ids.push(key); } } - groupsArrData.push({name: 'All', data: {lights: ids}}); + groupsArrData.push({name: 'All', data: {lights: ids, key: '0' }, rowClass: groupIdSelection === '0' ? 'groupRow selectedRow' : 'groupRow', deletable: false}); for (let key in groupsData) { if (groupsData.hasOwnProperty(key)) { - groupsArrData.push({name: groupsData[key].name, data: {lights: groupsData[key].lights, key: key}}); + var rowClass = 'groupRow'; + + if(key === groupIdSelection){ + rowClass += ' selectedRow'; + } + + groupsArrData.push({name: groupsData[key].name, data: {lights: groupsData[key].lights, key: key}, rowClass: rowClass, deletable: true}); } } return groupsArrData; - }.property('groupsData', 'lightsData'), + }.property('groupsData', 'lightsData', 'groupSelection'), - onGroupSelectionChanged: function(){ - var groupSelection = this.get('groupSelection'); + onGroupIdSelectionChanged: function(){ + var groupIdSelection = this.get('groupIdSelection'), lights = []; - if(!Em.isNone(groupSelection)){ - this.set('activeLights', groupSelection.lights); + this.get('groupsArrData').some(function(group){ + if(group.data.key === groupIdSelection){ + lights = group.data.lights; + return true; + } + }); + + if(!Em.isNone(groupIdSelection)){ + this.set('activeLights', lights); } - }.observes('groupSelection') + }.observes('groupIdSelection') }); diff --git a/app/components/controls/light-control.js b/app/components/controls/light-control.js index a746278..3defeaf 100644 --- a/app/components/controls/light-control.js +++ b/app/components/controls/light-control.js @@ -23,10 +23,14 @@ export default Em.Component.extend({ lightsOn: function(){ var lightsData = this.get('lightsData'); + if(this.get('strobeOn')){ + return false; + } + return this.get('activeLights').some(function(light) { return lightsData[light].state.on === true; }); - }.property('lightsData', 'activeLights'), + }.property('lightsData', 'activeLights', 'strobeOn'), // determines the average brightness of the hue system for the brightness slider lightsBrightness: function(){ @@ -83,5 +87,82 @@ export default Em.Component.extend({ lightsOnTxt: function(){ return this.get('lightsOn') ? 'On' : 'Off'; - }.property('lightsOn') + }.property('lightsOn'), + + + // **************** STROBE LIGHT START **************** + + strobeOn: false, + + strobeOnInervalHandle: null, + strobeSat: 0, + preStrobeOnLightsDataCache: null, + lastStrobeLight: 0, + + onStrobeOnChange: function () { + var lightsData = this.get('lightsData'), self = this; + + if (this.get('strobeOn')) { + this.set('preStrobeOnLightsDataCache', lightsData); + var stobeInitRequestData = {'sat': this.get('strobeSat'), 'transitiontime': 0}; + + for (let key in lightsData) { + if (lightsData.hasOwnProperty(key)) { + if (lightsData[key].state.on) { + stobeInitRequestData.on = false; + } + + Em.$.ajax(this.get('apiURL') + '/lights/' + key + '/state', { + data: JSON.stringify(stobeInitRequestData), + contentType: 'application/json', + type: 'PUT' + }); + } + } + + this.set('strobeOnInervalHandle', setInterval(this.strobeStep.bind(this), 200)); + } else { // revert the light system to pre-strobe + var preStrobeOnLightsDataCache = this.get('preStrobeOnLightsDataCache'), updateLight = function (lightIndx) { + Em.$.ajax(self.get('apiURL') + '/lights/' + lightIndx + '/state', { + data: JSON.stringify({ + 'on': preStrobeOnLightsDataCache[lightIndx].state.on, + 'sat': preStrobeOnLightsDataCache[lightIndx].state.sat + }), + contentType: 'application/json', + type: 'PUT' + }); + }; + + for (let key in lightsData) { + if (lightsData.hasOwnProperty(key)) { + setTimeout(updateLight, 2000, key); + } + } + + clearInterval(this.get('strobeOnInervalHandle')); + } + }.observes('strobeOn'), + + strobeStep: function () { + var lastStrobeLight = (this.get('lastStrobeLight') + 1) % (this.get('activeLights').length + 1), self = this; + + Em.$.ajax(this.get('apiURL') + '/lights/' + lastStrobeLight + '/state', { + data: JSON.stringify({'on': true, 'transitiontime': 0, 'alert': 'select'}), + contentType: 'application/json', + type: 'PUT' + }); + Em.$.ajax(self.get('apiURL') + '/lights/' + lastStrobeLight + '/state', { + data: JSON.stringify({'on': false, 'transitiontime': 0}), + contentType: 'application/json', + type: 'PUT' + }); + + this.set('lastStrobeLight', lastStrobeLight); + }, + + strobeOnTxt: function () { + return this.get('strobeOn') ? 'On' : 'Off'; + }.property('strobeOn') + + // **************** STROBE LIGHT FINISH **************** }); diff --git a/app/components/controls/music-control.js b/app/components/controls/music-control.js new file mode 100644 index 0000000..659e5c8 --- /dev/null +++ b/app/components/controls/music-control.js @@ -0,0 +1,13 @@ +import Em from 'ember'; + +export default Em.Component.extend({ + + apiURL: null, + + + + lightsData: null, + activeLights: null, + + +}); diff --git a/app/components/controls/party-control.js b/app/components/controls/party-control.js deleted file mode 100644 index 2826461..0000000 --- a/app/components/controls/party-control.js +++ /dev/null @@ -1,81 +0,0 @@ -import Em from 'ember'; - -export default Em.Component.extend({ - - apiURL: null, - - strobeOn: false, - - lightsData: null, - activeLights: null, - - strobeOnInervalHandle: null, - strobeSat: 0, - preStrobeOnLightsDataCache: null, - lastStrobeLight: 0, - - onStrobeOnChange: function () { - var lightsData = this.get('lightsData'), self = this; - - if (this.get('strobeOn')) { - this.set('preStrobeOnLightsDataCache', lightsData); - var stobeInitRequestData = {'sat': this.get('strobeSat'), 'transitiontime': 0}; - - for (let key in lightsData) { - if (lightsData.hasOwnProperty(key)) { - if (lightsData[key].state.on) { - stobeInitRequestData.on = false; - } - - Em.$.ajax(this.get('apiURL') + '/lights/' + key + '/state', { - data: JSON.stringify(stobeInitRequestData), - contentType: 'application/json', - type: 'PUT' - }); - } - } - - this.set('strobeOnInervalHandle', setInterval(this.strobeStep.bind(this), 200)); - } else { // revert the light system to pre-strobe - var preStrobeOnLightsDataCache = this.get('preStrobeOnLightsDataCache'), updateLight = function (lightIndx) { - Em.$.ajax(self.get('apiURL') + '/lights/' + lightIndx + '/state', { - data: JSON.stringify({ - 'on': preStrobeOnLightsDataCache[lightIndx].state.on, - 'sat': preStrobeOnLightsDataCache[lightIndx].state.sat - }), - contentType: 'application/json', - type: 'PUT' - }); - }; - - for (let key in lightsData) { - if (lightsData.hasOwnProperty(key)) { - setTimeout(updateLight, 2000, key); - } - } - - clearInterval(this.get('strobeOnInervalHandle')); - } - }.observes('strobeOn'), - - strobeStep: function () { - var lastStrobeLight = (this.get('lastStrobeLight') + 1) % (this.get('activeLights').length + 1), self = this; - - Em.$.ajax(this.get('apiURL') + '/lights/' + lastStrobeLight + '/state', { - data: JSON.stringify({'on': true, 'transitiontime': 0, 'alert': 'select'}), - contentType: 'application/json', - type: 'PUT' - }); - Em.$.ajax(self.get('apiURL') + '/lights/' + lastStrobeLight + '/state', { - data: JSON.stringify({'on': false, 'transitiontime': 0}), - contentType: 'application/json', - type: 'PUT' - }); - - this.set('lastStrobeLight', lastStrobeLight); - }, - - strobeOnTxt: function () { - return this.get('strobeOn') ? 'On' : 'Off'; - }.property('strobeOn') -}); diff --git a/app/components/light-group.js b/app/components/light-group.js index 8e20bde..d8a7a1a 100644 --- a/app/components/light-group.js +++ b/app/components/light-group.js @@ -1,13 +1,17 @@ import Em from 'ember'; -export default Em.Component.extend(Em.TargetActionSupport, { +export default Em.Component.extend({ actions: { clickLight: function(id, data){ - this.attrs.selectLight(id, data); + this.sendAction('action', id, data); }, lightStartHover: function(id){ - if(this.get('activeLights').contains(id)){ + var hoveredLight = this.get('lightsList').filter(function(light){ + return light.activeClass !== 'unreachable' && light.id === id[0]; + }); + + if(!Em.isEmpty(hoveredLight) && this.get('noHover') !== true){ Em.$.ajax(this.get('apiURL') + '/lights/' + id + '/state', { data: JSON.stringify({"alert": "lselect"}), contentType: 'application/json', @@ -16,7 +20,11 @@ export default Em.Component.extend(Em.TargetActionSupport, { } }, lightStopHover: function(id){ - if(this.get('activeLights').contains(id)){ + var hoveredLight = this.get('lightsList').filter(function(light){ + return light.activeClass !== 'unreachable' && light.id === id[0]; + }); + + if(!Em.isEmpty(hoveredLight) && this.get('noHover') !== true){ Em.$.ajax(this.get('apiURL') + '/lights/' + id + '/state', { data: JSON.stringify({"alert": "none"}), contentType: 'application/json', @@ -74,10 +82,18 @@ export default Em.Component.extend(Em.TargetActionSupport, { type = 'a19'; } - lightsList.push({type: type, name: lightsData[key].name, id: key, data: lightsData[key], activeClass: this.get('activeLights').contains(key) ? 'active' : 'inactive' }); + var activeClass = 'active'; + + if(!this.get('activeLights').contains(key)){ + activeClass = 'inactive'; + } else if(!lightsData[key].state.reachable){ + activeClass = 'unreachable'; + } + + lightsList.push({type: type, name: lightsData[key].name, id: key, data: lightsData[key], activeClass: activeClass}); } } return lightsList; - }.property('lightsData', 'activeLights') + }.property('lightsData', 'activeLights.[]') }); diff --git a/app/components/modals/add-group-modal.js b/app/components/modals/add-group-modal.js index 395340e..4dcb6d8 100644 --- a/app/components/modals/add-group-modal.js +++ b/app/components/modals/add-group-modal.js @@ -6,14 +6,44 @@ export default Em.Component.extend({ this.sendAction(); }, save: function(){ + var newGroupData = {"name": this.get('groupName'), "lights": this.get('selectedLights')}, newGroupsData = this.get('groupsData'); + + Em.$.ajax(this.get('apiURL') + '/groups', { + data: JSON.stringify(newGroupData), + contentType: 'application/json', + type: 'POST' + }); + + newGroupsData['9999'] = newGroupData; + + this.setProperties({ + updateGroupsData: true, + groupsData: newGroupsData + }); this.sendAction(); }, - selectLight: function(id) { - console.log('selected ' + id); + clickLight: function(id) { + var selectedLights = this.get('selectedLights'); + + if(selectedLights.contains(id)){ + selectedLights.removeObject(id); + } else { + selectedLights.push(id); + } + + this.notifyPropertyChange('selectedLights'); } }, groupName: null, - saveDisabled: false + selectedLights: [], + + onIsShowingAddGroupsModalChange: function(){ + this.set('selectedLights', []); + }.observes('isShowingAddGroupsModal'), + + saveDisabled: function(){ + return Em.isNone(this.get('groupName')) || Em.isEmpty(this.get('selectedLights')) || Em.isEmpty(this.get('groupName').trim()); + }.property('groupName', 'selectedLights.[]') }); diff --git a/app/components/modals/confirm-delete-modal.js b/app/components/modals/confirm-delete-modal.js new file mode 100644 index 0000000..cb65cd6 --- /dev/null +++ b/app/components/modals/confirm-delete-modal.js @@ -0,0 +1,29 @@ +import Em from 'ember'; + +export default Em.Component.extend({ + actions: { + close: function(){ + this.sendAction(); + }, + delete: function(){ + Em.$.ajax(this.get('apiURL') + '/groups/' + this.get('groupId'), { + contentType: 'application/json', + type: 'DELETE' + }); + + var groupsData = this.get('groupsData'), newGroupsData = []; + for (let key in groupsData) { + if(groupsData.hasOwnProperty(key) && groupsData[key].name !== this.get('groupName') ){ + newGroupsData[key] = groupsData[key]; + } + } + + this.setProperties({ + updateGroupsData: true, + groupsData: newGroupsData + }); + + this.sendAction(); + } + } +}); diff --git a/app/styles/app.scss b/app/styles/app.scss index 591bc56..124d61c 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -27,6 +27,11 @@ md-list { } .hueLight.inactive { + cursor: pointer; + background-color: rgba(192, 192, 192, 0.7); +} + +.hueLight.unreachable { background-color: rgba(255, 0, 0, 0.7); } @@ -49,21 +54,22 @@ md-icon { } md-icon.menu { - margin-top: 10px; + margin: 10px 16px 0 0; } .addButton { cursor: pointer; - margin-left: 30px; + float: right; + margin-right: 16px; } .removeButton { cursor: pointer; - margin: 0 0 0 auto !important; + margin: 10px 0 10px auto; } .sideNavTitle { - margin-left: 10px; + margin-left: 16px; } md-toolbar { @@ -74,3 +80,17 @@ md-toolbar { background-color: mintcream; height: 100vh; } + +.groupRow.selectedRow{ + background-color: lightgrey !important; +} + +.groupRow:hover { + background-color: #E6E6E6; +} + +.groupSelect { + padding: 10px 0 10px 0; + cursor: pointer; + width: 70%; +} diff --git a/app/templates/components/bridge-controls.hbs b/app/templates/components/bridge-controls.hbs index 5428532..ec02b8e 100644 --- a/app/templates/components/bridge-controls.hbs +++ b/app/templates/components/bridge-controls.hbs @@ -15,8 +15,8 @@ {{controls/scene-control apiURL=apiURL lightsData=lightsData activeLights=activeLights}} {{/liquid-if}} - {{#liquid-if partyTabSelected class="tabSwitch"}} - {{controls/party-control apiURL=apiURL lightsData=lightsData activeLights=activeLights}} + {{#liquid-if musicTabSelected class="tabSwitch"}} + {{controls/music-control apiURL=apiURL lightsData=lightsData activeLights=activeLights}} {{/liquid-if}} {{/paper-content}} @@ -25,6 +25,6 @@ {{/paper-sidenav-toggle}} {{#paper-sidenav class="md-sidenav-right md-whiteframe-z2" flex-layout="column" flex=true}} - {{controls/group-control lightsData=lightsData groupsData=groupsData activeLights=activeLights}} + {{controls/group-control lightsData=lightsData groupsData=groupsData activeLights=activeLights apiURL=apiURL updateGroupsData=updateGroupsData}} {{/paper-sidenav}} {{/paper-nav-container}} \ No newline at end of file diff --git a/app/templates/components/controls/group-control.hbs b/app/templates/components/controls/group-control.hbs index 79d25b0..6bbe22e 100644 --- a/app/templates/components/controls/group-control.hbs +++ b/app/templates/components/controls/group-control.hbs @@ -3,11 +3,14 @@ {{#paper-list}} {{#each groupsArrData as |group|}} - {{#paper-item}} - {{group.name}} {{#if group.data.key}}{{paper-icon icon="remove"}}{{/if}} + {{#paper-item class=group.rowClass}} +
{{group.name}}
{{#if group.deletable}}{{paper-icon icon="remove"}}{{/if}} {{/paper-item}} {{/each}} {{/paper-list}} {{/paper-content}} -{{modals/add-group-modal lightsData=lightsData groupsData=groupsData activeLights=activeLights apiURL=apiURL action="toggleAddGroupsModal" isShowingAddGroupsModal=isShowingAddGroupsModal}} \ No newline at end of file +{{modals/add-group-modal lightsData=lightsData groupsData=groupsData isShowingAddGroupsModal=isShowingAddGroupsModal apiURL=apiURL updateGroupsData=updateGroupsData +action="toggleAddGroupsModal"}} + +{{modals/confirm-delete-modal groupName=deleteGroupName groupId=deleteGroupId groupsData=groupsData isShowingConfirmDeleteModal=isShowingConfirmDeleteModal apiURL=apiURL updateGroupsData=updateGroupsData action="toggleConfirmDeleteGroupsModal"}} \ No newline at end of file diff --git a/app/templates/components/controls/light-control.hbs b/app/templates/components/controls/light-control.hbs index 0656267..309be73 100644 --- a/app/templates/components/controls/light-control.hbs +++ b/app/templates/components/controls/light-control.hbs @@ -1,7 +1,7 @@ {{#paper-list}} {{#paper-item class="item"}} - {{light-group lightsData=lightsData activeLights=activeLights selectLight=(action 'selectLight') apiURL=apiURL}} + {{light-group lightsData=lightsData activeLights=activeLights action='clickLight' apiURL=apiURL}} {{/paper-item}} {{#paper-item class="item"}} @@ -16,6 +16,12 @@ {{paper-slider flex=true min='1' max='254' value=lightsBrightness disabled=brightnessControlDisabled}} {{/paper-item}} - {{modals/light-control-modal modalData=modalData apiURL=apiURL action="toggleLightModal" isShowingLightsModal=isShowingLightsModal}} + {{#paper-item class="item"}} + {{paper-icon icon="flare"}} +

Strobe

+ {{#paper-switch checked=strobeOn}} {{strobeOnTxt}} {{/paper-switch}} + {{/paper-item}} + + {{modals/light-control-modal modalData=modalData apiURL=apiURL action="selectLight" isShowingLightsModal=isShowingLightsModal}} {{/paper-list}} \ No newline at end of file diff --git a/app/templates/components/controls/party-control.hbs b/app/templates/components/controls/music-control.hbs similarity index 54% rename from app/templates/components/controls/party-control.hbs rename to app/templates/components/controls/music-control.hbs index 532edf2..9e8f41e 100644 --- a/app/templates/components/controls/party-control.hbs +++ b/app/templates/components/controls/music-control.hbs @@ -1,11 +1,5 @@ {{#paper-list}} - {{#paper-item class="item"}} - {{paper-icon icon="flare"}} -

Strobe

- {{#paper-switch checked=strobeOn}} {{strobeOnTxt}} {{/paper-switch}} - {{/paper-item}} - {{#paper-item class="item"}} {{paper-icon icon="music-note"}}

Music

diff --git a/app/templates/components/light-group.hbs b/app/templates/components/light-group.hbs index 2365980..8192a71 100644 --- a/app/templates/components/light-group.hbs +++ b/app/templates/components/light-group.hbs @@ -1,3 +1,3 @@ {{#each lightsList as |light|}} - + {{/each}} \ No newline at end of file diff --git a/app/templates/components/modals/add-group-modal.hbs b/app/templates/components/modals/add-group-modal.hbs index cf5ae7f..56a16ed 100644 --- a/app/templates/components/modals/add-group-modal.hbs +++ b/app/templates/components/modals/add-group-modal.hbs @@ -1,14 +1,12 @@ {{#if isShowingAddGroupsModal}} - {{#modal-dialog close="close" - alignment="center" - translucentOverlay=true}} + {{#modal-dialog close="close" alignment="center" translucentOverlay=true}} - {{light-group lightsData=lightsData activeLights=activeLights selectLight=(action 'selectLight') apiURL=apiURL}} + {{light-group lightsData=lightsData activeLights=selectedLights action= 'clickLight' apiURL=apiURL noHover=true}} {{paper-input label="Group name" value=groupName max="32" max-errortext="The group name cannot exceed 32 characters"}} - - + {{#paper-button action="close"}}Close{{/paper-button}} + {{#paper-button class="pull-right" action="save" disabled=saveDisabled primary=true}}Save{{/paper-button}} {{/modal-dialog}} {{/if}} \ No newline at end of file diff --git a/app/templates/components/modals/confirm-delete-modal.hbs b/app/templates/components/modals/confirm-delete-modal.hbs new file mode 100644 index 0000000..359929f --- /dev/null +++ b/app/templates/components/modals/confirm-delete-modal.hbs @@ -0,0 +1,10 @@ +{{#if isShowingConfirmDeleteModal}} + {{#modal-dialog close="close" alignment="center" translucentOverlay=true}} + +

Are you sure you want to delete group "{{groupName}}"?

+ + {{#paper-button action="close"}}Close{{/paper-button}} + {{#paper-button class="pull-right" action="delete" primary=true}}Delete{{/paper-button}} + + {{/modal-dialog}} +{{/if}} \ No newline at end of file diff --git a/tests/integration/components/modals/confirm-delete-modal-test.js b/tests/integration/components/modals/confirm-delete-modal-test.js new file mode 100644 index 0000000..a56e524 --- /dev/null +++ b/tests/integration/components/modals/confirm-delete-modal-test.js @@ -0,0 +1,26 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('modals/confirm-delete-modal', 'Integration | Component | modals/confirm delete modal', { + integration: true +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + + this.render(hbs`{{modals/confirm-delete-modal}}`); + + assert.equal(this.$().text().trim(), ''); + + // Template block usage: + this.render(hbs` + {{#modals/confirm-delete-modal}} + template block text + {{/modals/confirm-delete-modal}} + `); + + assert.equal(this.$().text().trim(), 'template block text'); +});