ha-frontend/src/layouts/partial-cards.html

312 lines
9.2 KiB
HTML

<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="../../bower_components/iron-icon/iron-icon.html">
<link rel="import" href="../../bower_components/iron-pages/iron-pages.html">
<link rel="import" href="../../bower_components/paper-tabs/paper-tabs.html">
<link rel="import" href="../../bower_components/paper-tabs/paper-tab.html">
<link rel="import" href="../../bower_components/app-layout/app-header-layout/app-header-layout.html">
<link rel="import" href="../../bower_components/app-layout/app-scroll-effects/effects/waterfall.html">
<link rel="import" href="../../bower_components/app-layout/app-header/app-header.html">
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
<link rel="import" href="../components/ha-menu-button.html">
<link rel="import" href="../components/ha-start-voice-button.html">
<link rel="import" href="../components/ha-cards.html">
<dom-module id="partial-cards">
<template>
<style include="iron-flex iron-positioning ha-style">
:host {
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
}
app-header-layout {
background-color: #E5E5E5;
}
paper-tabs {
margin-left: 12px;
--paper-tabs-selection-bar-color: #FFF;
text-transform: uppercase;
}
</style>
<app-header-layout has-scrolling-region id='layout'>
<app-header effects="waterfall" condenses fixed>
<app-toolbar>
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
<div main-title>[[computeTitle(views, locationName)]]</div>
<ha-start-voice-button hass='[[hass]]'></ha-start-voice-button>
</app-toolbar>
<div sticky hidden$='[[!views.length]]'>
<paper-tabs
scrollable
selected='[[currentView]]'
attr-for-selected='data-entity'
on-iron-select='handleViewSelected'
>
<paper-tab
data-entity=''
on-tap='scrollToTop'
>[[locationName]]</paper-tab>
<template is='dom-repeat' items='[[views]]'>
<paper-tab
data-entity$='[[item.entity_id]]'
on-tap='scrollToTop'
>
<template is='dom-if' if='[[item.attributes.icon]]'>
<iron-icon icon='[[item.attributes.icon]]'></iron-icon>
</template>
<template is='dom-if' if='[[!item.attributes.icon]]'>
[[computeStateName(item)]]
</template>
</paper-tab>
</template>
</paper-tabs>
</div>
</app-header>
<iron-pages
attr-for-selected='data-view'
selected='[[currentView]]'
selected-attribute='view-visible'
>
<ha-cards
data-view=''
show-introduction='[[computeShowIntroduction(currentView, introductionLoaded, viewStates)]]'
states='[[viewStates]]'
columns='[[_columns]]'
hass='[[hass]]'
panel-visible='[[panelVisible]]'
></ha-cards>
<template is='dom-repeat' items='[[views]]'>
<ha-cards
data-view$='[[item.entity_id]]'
show-introduction='[[computeShowIntroduction(currentView, introductionLoaded, viewStates)]]'
states='[[viewStates]]'
columns='[[_columns]]'
hass='[[hass]]'
panel-visible='[[panelVisible]]'
></ha-cards>
</template>
</iron-pages>
</app-header-layout>
</template>
</dom-module>
<script>
Polymer({
DEFAULT_VIEW_ENTITY_ID: 'group.default_view',
ALWAYS_SHOW_DOMAIN: ['persistent_notification', 'configurator'],
is: 'partial-cards',
properties: {
hass: {
type: Object,
value: null,
},
narrow: {
type: Boolean,
value: false,
},
showMenu: {
type: Boolean,
observer: 'handleWindowChange',
},
panelVisible: {
type: Boolean,
value: false,
},
_columns: {
type: Number,
value: 1,
},
introductionLoaded: {
type: Boolean,
computed: 'computeIntroductionLoaded(hass)',
},
locationName: {
type: String,
value: '',
computed: 'computeLocationName(hass)',
},
currentView: {
type: String,
computed: 'computeCurrentView(hass)',
},
views: {
type: Array,
computed: 'computeViews(hass)',
},
viewStates: {
type: Object,
computed: 'computeViewStates(currentView, hass)',
},
},
created: function () {
this.handleWindowChange = this.handleWindowChange.bind(this);
this.mqls = [300, 600, 900, 1200].map(function (width) {
var mql = window.matchMedia('(min-width: ' + width + 'px)');
mql.addListener(this.handleWindowChange);
return mql;
}.bind(this));
},
detached: function () {
this.mqls.forEach(function (mql) {
mql.removeListener(this.handleWindowChange);
});
},
handleWindowChange: function () {
var matchColumns = this.mqls.reduce(function (cols, mql) {
return cols + mql.matches;
}, 0);
// Do -1 column if the menu is docked and open
this._columns = Math.max(1, matchColumns - (!this.narrow && this.showMenu));
},
/**
* Scroll to a specific y coordinate.
*
* Copied from paper-scroll-header-panel.
*
* @method scroll
* @param {number} top The coordinate to scroll to, along the y-axis.
* @param {boolean} smooth true if the scroll position should be smoothly adjusted.
*/
scrollToTop: function () {
// the scroll event will trigger _updateScrollState directly,
// However, _updateScrollState relies on the previous `scrollTop` to update the states.
// Calling _updateScrollState will ensure that the states are synced correctly.
var top = 0;
var scroller = this.$.layout.header.scrollTarget;
var easingFn = function easeOutQuad(t, b, c, d) {
/* eslint-disable no-param-reassign, space-infix-ops, no-mixed-operators */
t /= d;
return -c * t*(t-2) + b;
/* eslint-enable no-param-reassign, space-infix-ops, no-mixed-operators */
};
var animationId = Math.random();
var duration = 200;
var startTime = Date.now();
var currentScrollTop = scroller.scrollTop;
var deltaScrollTop = top - currentScrollTop;
this._currentAnimationId = animationId;
(function updateFrame() {
var now = Date.now();
var elapsedTime = now - startTime;
if (elapsedTime > duration) {
scroller.scrollTop = top;
} else if (this._currentAnimationId === animationId) {
scroller.scrollTop = easingFn(elapsedTime, currentScrollTop, deltaScrollTop, duration);
requestAnimationFrame(updateFrame.bind(this));
}
}).call(this);
},
handleViewSelected: function (ev) {
var view = ev.detail.item.getAttribute('data-entity') || null;
var current = this.currentView || null;
if (view !== current) {
this.fire('hass-navigate', { view: view });
}
},
computeTitle: function (views, locationName) {
return views.length > 0 ? 'Home Assistant' : locationName;
},
computeShowIntroduction: function (currentView, introductionLoaded, states) {
return currentView === '' && (introductionLoaded || states.size === 0);
},
computeStateName: function (stateObj) {
return window.hassUtil.computeStateName(stateObj);
},
computeLocationName: function (hass) {
return window.hassUtil.computeLocationName(hass);
},
computeIntroductionLoaded: function (hass) {
return window.hassUtil.isComponentLoaded(hass, 'introduction');
},
computeViews: function (hass) {
return window.HAWS.extractViews(hass.states);
},
/*
Compute the states to show for current view.
Will make sure we always show entities from ALWAYS_SHOW_DOMAINS domains.
*/
computeViewStates: function (currentView, hass) {
var i;
var entityId;
var state;
var states;
var entityIds = Object.keys(hass.states);
// If we base off all entities, only have to filter out hidden
if (!currentView && !(this.DEFAULT_VIEW_ENTITY_ID in hass.states)) {
states = {};
for (i = 0; i < entityIds.length; i++) {
entityId = entityIds[i];
state = hass.states[entityId];
// We can filter out hidden and domain at the same time.
if (!state.attributes.hidden) {
states[entityId] = state;
}
}
return states;
}
if (currentView) {
states = window.HAWS.getViewEntities(hass.states, hass.states[currentView]);
} else {
states = window.HAWS.getViewEntities(
hass.states, hass.states[this.DEFAULT_VIEW_ENTITY_ID]);
}
// Make sure certain domains are always shown.
for (i = 0; i < entityIds.length; i++) {
entityId = entityIds[i];
state = hass.states[entityId];
if (this.ALWAYS_SHOW_DOMAIN.indexOf(window.hassUtil.computeDomain(state)) !== -1) {
states[entityId] = state;
}
}
return states;
},
computeCurrentView: function (hass) {
return hass.currentView || '';
},
});
</script>