Skip to content
This repository has been archived by the owner on Nov 4, 2023. It is now read-only.

refactor(CLIMATE): create separate directive #706

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ const config = {
babelHelpers: 'bundled',
exclude: [
'node_modules/**',
'scripts/directives/*.html',
'scripts/directives/**/*.html',
],
}),
html({
include: 'scripts/directives/*.html',
include: [
'scripts/directives/**/*.html',
],
}),
styles({
// Extract CSS into separate file (path specified through output.assetFileNames).
Expand Down
124 changes: 9 additions & 115 deletions scripts/controllers/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -1313,121 +1313,6 @@ App.controller('Main', function ($scope, $timeout, $location, Api, tmhDynamicLoc
return false;
};

$scope.climateTarget = function (item, entity) {
const value = entity.attributes.temperature || [
entity.attributes.target_temp_low,
entity.attributes.target_temp_high,
].join(' - ');

if (item.filter) {
return item.filter(value);
}

return value;
};

$scope.reverseLookupClimateOption = function (item, entity, option) {
initializeClimateOptions(item, entity);
for (const [key, value] of Object.entries(item.climateOptions)) {
if (value === option) {
return key;
}
}
return option;
};

$scope.lookupClimateOption = function (item, entity, option) {
initializeClimateOptions(item, entity);
return item.climateOptions[option] || option;
};

function initializeClimateOptions (item, entity) {
if (typeof item.climateOptions === 'undefined') {
const options = item.useHvacMode ? entity.attributes.hvac_modes : entity.attributes.preset_modes;
const resolvedOption = {};
for (const option of options) {
if (item.states !== null && typeof item.states === 'object') {
resolvedOption[option] = item.states[option] || option;
} else {
resolvedOption[option] = option;
}
}
item.climateOptions = resolvedOption;
}
}

$scope.getClimateOptions = function (item, entity) {
initializeClimateOptions(item, entity);
return item.climateOptions;
};

$scope.getClimateCurrentOption = function (item, entity) {
const option = item.useHvacMode ? entity.state : entity.attributes.preset_mode;
return $scope.lookupClimateOption(item, entity, option);
};

$scope.setClimateOption = function ($event, item, entity, option) {
$event.preventDefault();
$event.stopPropagation();

let service;
const serviceData = {};
const resolvedOption = $scope.reverseLookupClimateOption(item, entity, option);

if (item.useHvacMode) {
service = 'set_hvac_mode';
serviceData.hvac_mode = resolvedOption;
} else {
service = 'set_preset_mode';
serviceData.preset_mode = resolvedOption;
}

callService(item, 'climate', service, serviceData);

$scope.closeActiveSelect();

return false;
};


$scope.increaseClimateTemp = function ($event, item, entity) {
$event.preventDefault();
$event.stopPropagation();

let value = parseFloat(entity.attributes.temperature);

value += (entity.attributes.target_temp_step || 1);

if (entity.attributes.max_temp) {
value = Math.min(value, entity.attributes.max_temp);
}

$scope.setClimateTemp(item, value);

return false;
};

$scope.decreaseClimateTemp = function ($event, item, entity) {
$event.preventDefault();
$event.stopPropagation();

let value = parseFloat(entity.attributes.temperature);

value -= (entity.attributes.target_temp_step || 1);

if (entity.attributes.min_temp) {
value = Math.max(value, entity.attributes.min_temp);
}

$scope.setClimateTemp(item, value);

return false;
};

$scope.setClimateTemp = function (item, value) {
callService(item, 'climate', 'set_temperature', { temperature: value });
};

$scope.sendCover = function (service, item, entity) {
callService(item, 'cover', service, {});
};
Expand Down Expand Up @@ -2500,4 +2385,13 @@ App.controller('Main', function ($scope, $timeout, $location, Api, tmhDynamicLoc
pingConnection();
});
}

// Temporary transitioning APIs to be able to access shared APIs from directives.

this.callService = callService;
this.closeActiveSelect = $scope.closeActiveSelect;
this.entityIcon = $scope.entityIcon;
this.itemSelectStyles = $scope.itemSelectStyles;
this.openSelect = $scope.openSelect;
this.selectOpened = $scope.selectOpened;
});
2 changes: 2 additions & 0 deletions scripts/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import ngMax from './directives/ngMax';
import ngMin from './directives/ngMin';
import onScroll from './directives/onScroll';
import tile from './directives/tile';
// Register tile directives.
import './directives/tiles';

App.directive('camera', camera);
App.directive('cameraStream', cameraStream);
Expand Down
50 changes: 7 additions & 43 deletions scripts/directives/tile.html
Original file line number Diff line number Diff line change
Expand Up @@ -518,49 +518,13 @@
</div>
</div>

<div ng-if="item.type === TYPES.CLIMATE" class="item-entity-container">
<div>
<div
class="item-button -center-right"
ng-if="entity.attributes.temperature && entity.state !== 'off'"
ng-click="increaseClimateTemp($event, item, entity)"
>
<i class="mdi mdi-plus"></i>
</div>
<div
class="item-button -bottom-right"
ng-if="entity.attributes.temperature && entity.state !== 'off'"
ng-click="decreaseClimateTemp($event, item, entity)"
>
<i class="mdi mdi-minus"></i>
</div>
</div>

<div class="item-climate">
<div class="item-climate--target">
<span ng-bind="climateTarget(item, entity)"></span>
<span ng-if="(_unit = entityUnit(item, entity))" class="item-climate--target--unit" ng-bind="_unit"></span>
<i class="item-climate--icon mdi" ng-class="entityIcon(item, entity)"></i>
</div>
<div
class="item-climate--mode"
ng-if="(_options = getClimateOptions(item, entity))"
ng-click="openSelect(item)"
>
<span ng-bind="getClimateCurrentOption(item, entity)"></span>
</div>
</div>
<div ng-if="selectOpened(item)" class="item-select" ng-style="itemSelectStyles(entity, Object.keys(_options))">
<div
class="item-select--option"
ng-repeat="option in _options track by $index"
ng-class="{'-active': option === lookupClimateOption(item, entity, entity.state)}"
ng-click="setClimateOption($event, item, entity, option)"
>
<span ng-bind="option"></span>
</div>
</div>
</div>
<div
ng-if="item.type === TYPES.CLIMATE"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we get rid of those specialized code sections completely in tile.html? Kind of include all tile templates from directory ...?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I also thought that would look weird once all tiles are converted to have these repetitive elements here.

I think it could be solved with some directive that would dynamically insert a relevant directive based on the type. My angularjs is a bit rusty but it should be doable, I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good idea. This would also pave the way to completely custom tile types user can create themselves and share if they want.

class="item-entity-container"
tile-climate
item="item"
entity="entity"
></div>

<div ng-if="item.type === TYPES.MEDIA_PLAYER" class="item-entity-container">
<div class="media-player-table" ng-class="{'-has-state': _state, '-has-subtitle': _subtitle}">
Expand Down
2 changes: 1 addition & 1 deletion scripts/directives/tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function () {
return {
restrict: 'AE',
replace: false,
scope: true,
scope: false,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we needed to create extra scope for each tile. Maybe we did since we are doing stuff like ng-if="_foo = fn()" and that avoided possible overwrites. Will have to test how this works exactly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this influence, if generic function from the main controller are available? I'm thinking of things such as cacheInItem or the function you aliased in "Temporary transitioning APIs".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scope: true creates a new scope that inherits from the parent scope. So everything from the parent scope is still accessible. It can only affect things when setting properties (like in the case I've mentioned).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And scope: false of course runs in the same scope so everything is accessible.

template,
};
}
4 changes: 4 additions & 0 deletions scripts/directives/tiles/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { App } from '../../app';
import tileClimate from './tileClimate';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here: Can we generically include all tile types? Alternatively: We could add the html part of the template here, so we don't have two places to add a new tile type...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing all tile files at once might not be doable with ES imports since imports are hoisted and run before any code so we can't read files from disk and then import based on that. But we could do some preprocessing with rollup to generate files with everything imported, maybe.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's of lesser importance and we should solve the html part first. (Maybe with said automatic/dynamic directives you mentioned.)


App.directive('tileClimate', tileClimate);
46 changes: 46 additions & 0 deletions scripts/directives/tiles/tileClimate.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<div>
<div
class="item-button -center-right"
ng-if="ctrl.entity.attributes.temperature && ctrl.entity.state !== 'off'"
ng-click="ctrl.increaseClimateTemp($event)"
>
<i class="mdi mdi-plus"></i>
</div>
<div
class="item-button -bottom-right"
ng-if="ctrl.entity.attributes.temperature && ctrl.entity.state !== 'off'"
ng-click="ctrl.decreaseClimateTemp($event)"
>
<i class="mdi mdi-minus"></i>
</div>
</div>
<div class="item-climate">
<div class="item-climate--target">
<span>{{ ctrl.climateTarget() }}</span>
<span ng-if="(_unit = ctrl.rootCtrl.entityUnit(ctrl.item, ctrl.entity))" class="item-climate--target--unit"
>{{ _unit }}</span
>
<i class="item-climate--icon mdi" ng-class="ctrl.rootCtrl.entityIcon(ctrl.item, ctrl.entity)"></i>
</div>
<div
class="item-climate--mode"
ng-if="(_options = ctrl.getClimateOptions())"
ng-click="ctrl.rootCtrl.openSelect(ctrl.item)"
>
<span>{{ ctrl.getClimateCurrentOption() }}</span>
</div>
</div>
<div
ng-if="ctrl.rootCtrl.selectOpened(ctrl.item)"
class="item-select"
ng-style="ctrl.rootCtrl.itemSelectStyles(ctrl.entity, Object.keys(_options))"
>
<div
class="item-select--option"
ng-repeat="option in _options track by $index"
ng-class="{'-active': option === ctrl.lookupClimateOption(ctrl.entity.state)}"
ng-click="ctrl.setClimateOption($event, option)"
>
<span>{{ option }}</span>
</div>
</div>
Loading