Use app-route for routing (#328)

* Use app-route for routing

* Fix bower

* Fix default route

* Add back button support back
This commit is contained in:
Paulus Schoutsen 2017-07-06 21:06:07 -07:00 committed by GitHub
parent 33ee6a9075
commit 10d5a05d1f
7 changed files with 142 additions and 137 deletions

View File

@ -9,6 +9,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"app-layout": "^2.0.0", "app-layout": "^2.0.0",
"app-route": "PolymerElements/app-route#^2.0.0",
"app-storage": "^2.0.2", "app-storage": "^2.0.2",
"fecha": "~2.3.0", "fecha": "~2.3.0",
"font-roboto-local": "~1.0.1", "font-roboto-local": "~1.0.1",
@ -70,6 +71,7 @@
"iron-input": "^2.0.0", "iron-input": "^2.0.0",
"iron-jsonp-library": "^2.0.0", "iron-jsonp-library": "^2.0.0",
"iron-list": "^2.0.0", "iron-list": "^2.0.0",
"iron-location": "^2.0.1",
"iron-media-query": "^2.0.0", "iron-media-query": "^2.0.0",
"iron-menu-behavior": "^2.0.0", "iron-menu-behavior": "^2.0.0",
"iron-meta": "^2.0.0", "iron-meta": "^2.0.0",
@ -103,7 +105,6 @@
"paper-scroll-header-panel": "^2.0.0", "paper-scroll-header-panel": "^2.0.0",
"paper-slider": "^2.0.0", "paper-slider": "^2.0.0",
"paper-spinner": "^2.0.0", "paper-spinner": "^2.0.0",
"paper-styles": "2.0.0",
"paper-styles": "^2.0.0", "paper-styles": "^2.0.0",
"paper-tabs": "^2.0.0", "paper-tabs": "^2.0.0",
"paper-toast": "^2.0.0", "paper-toast": "^2.0.0",

View File

@ -99,7 +99,7 @@
<paper-icon-button icon='mdi:chevron-left' hidden$='[[narrow]]' on-tap='toggleMenu'></paper-icon-button> <paper-icon-button icon='mdi:chevron-left' hidden$='[[narrow]]' on-tap='toggleMenu'></paper-icon-button>
</app-toolbar> </app-toolbar>
<paper-listbox attr-for-selected='data-panel' selected='[[hass.currentPanel]]' on-iron-select='menuSelect'> <paper-listbox attr-for-selected='data-panel' selected='[[route.panel]]'>
<paper-icon-item on-tap='menuClicked' data-panel='states'> <paper-icon-item on-tap='menuClicked' data-panel='states'>
<iron-icon slot="item-icon" icon='mdi:apps'></iron-icon> <iron-icon slot="item-icon" icon='mdi:apps'></iron-icon>
<span class='item-text'>States</span> <span class='item-text'>States</span>
@ -178,9 +178,9 @@ Polymer({
type: String, type: String,
}, },
narrow: { narrow: Boolean,
type: Boolean,
}, route: Object,
panels: { panels: {
type: Array, type: Array,
@ -193,10 +193,6 @@ Polymer({
}, },
}, },
created: function () {
this._boundUpdateStyles = this.updateStyles.bind(this);
},
computePanels: function (hass) { computePanels: function (hass) {
var panels = hass.config.panels; var panels = hass.config.panels;
var sortValue = { var sortValue = {
@ -236,10 +232,6 @@ Polymer({
return result; return result;
}, },
menuSelect: function () {
this.debounce('updateStyles', this._boundUpdateStyles, 1);
},
menuClicked: function (ev) { menuClicked: function (ev) {
var target = ev.target; var target = ev.target;
var checks = 5; var checks = 5;
@ -262,14 +254,16 @@ Polymer({
}, },
selectPanel: function (newChoice) { selectPanel: function (newChoice) {
if (newChoice === this.hass.currentPanel) { if (newChoice === 'logout') {
return;
} else if (newChoice === 'logout') {
this.handleLogOut(); this.handleLogOut();
return; return;
} }
this.fire('hass-navigate', { panel: newChoice }); var path = '/' + newChoice;
this.debounce('updateStyles', this._boundUpdateStyles, 1); if (path === document.location.pathname) {
return;
}
history.pushState(null, null, path);
this.fire('location-changed');
}, },
handleLogOut: function () { handleLogOut: function () {

View File

@ -25,7 +25,6 @@
<template is='dom-if' if='[[showMain]]' restamp> <template is='dom-if' if='[[showMain]]' restamp>
<home-assistant-main <home-assistant-main
on-hass-more-info='handleMoreInfo' on-hass-more-info='handleMoreInfo'
on-hass-navigate='handleNavigate'
on-hass-dock-sidebar='handleDockSidebar' on-hass-dock-sidebar='handleDockSidebar'
on-hass-notification='handleNotification' on-hass-notification='handleNotification'
on-hass-logout='handleLogout' on-hass-logout='handleLogout'
@ -127,8 +126,6 @@ Polymer({
states: null, states: null,
config: null, config: null,
dockedSidebar: false, dockedSidebar: false,
currentPanel: 'states',
currentView: null,
moreInfoEntityId: null, moreInfoEntityId: null,
callService: function (domain, service, serviceData) { callService: function (domain, service, serviceData) {
return conn.callService(domain, service, serviceData || {}) return conn.callService(domain, service, serviceData || {})
@ -217,22 +214,6 @@ Polymer({
{ moreInfoEntityId: ev.detail.entityId }); { moreInfoEntityId: ev.detail.entityId });
}, },
handleNavigate: function (ev) {
ev.stopPropagation();
var hass = Object.assign({}, this.hass);
if ('panel' in ev.detail) {
hass.currentPanel = ev.detail.panel;
}
if ('view' in ev.detail) {
hass.currentView = ev.detail.view;
}
this.hass = hass;
},
handleDockSidebar: function (ev) { handleDockSidebar: function (ev) {
ev.stopPropagation(); ev.stopPropagation();
this.hass = Object.assign( this.hass = Object.assign(

View File

@ -3,6 +3,10 @@
<link rel='import' href='../../bower_components/iron-media-query/iron-media-query.html'> <link rel='import' href='../../bower_components/iron-media-query/iron-media-query.html'>
<link rel='import' href='../../bower_components/iron-pages/iron-pages.html'> <link rel='import' href='../../bower_components/iron-pages/iron-pages.html'>
<link rel='import' href='../../bower_components/app-route/app-route.html'>
<link rel='import' href='../../bower_components/app-route/app-location.html'>
<link rel='import' href='../layouts/partial-cards.html'> <link rel='import' href='../layouts/partial-cards.html'>
<link rel='import' href='../layouts/partial-panel-resolver.html'> <link rel='import' href='../layouts/partial-panel-resolver.html'>
<link rel="import" href="../dialogs/more-info-dialog.html"> <link rel="import" href="../dialogs/more-info-dialog.html">
@ -15,6 +19,13 @@
<template> <template>
<more-info-dialog hass='[[hass]]'></more-info-dialog> <more-info-dialog hass='[[hass]]'></more-info-dialog>
<ha-url-sync hass='[[hass]]'></ha-url-sync> <ha-url-sync hass='[[hass]]'></ha-url-sync>
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/:panel"
data="{{routeData}}"
tail="{{routeTail}}"
></app-route>
<ha-voice-command-dialog <ha-voice-command-dialog
hass='[[hass]]' hass='[[hass]]'
id='voiceDialog' id='voiceDialog'
@ -26,13 +37,18 @@
force-narrow='[[computeForceNarrow(narrow, dockedSidebar)]]' force-narrow='[[computeForceNarrow(narrow, dockedSidebar)]]'
responsive-width='0' disable-swipe='[[isSelectedMap]]' responsive-width='0' disable-swipe='[[isSelectedMap]]'
disable-edge-swipe='[[isSelectedMap]]'> disable-edge-swipe='[[isSelectedMap]]'>
<ha-sidebar slot="drawer" narrow='[[narrow]]' hass='[[hass]]'></ha-sidebar> <ha-sidebar
slot="drawer"
narrow='[[narrow]]'
hass='[[hass]]'
route='[[routeData]]'
></ha-sidebar>
<iron-pages <iron-pages
slot="main" slot="main"
attr-for-selected='id' attr-for-selected='id'
fallback-selection='panel-resolver' fallback-selection='panel-resolver'
selected='[[currentPanel]]' selected='[[_computeSelected(routeData)]]'
selected-attribute='panel-visible' selected-attribute='panel-visible'
> >
<partial-cards <partial-cards
@ -40,12 +56,14 @@
narrow='[[narrow]]' narrow='[[narrow]]'
hass='[[hass]]' hass='[[hass]]'
show-menu='[[dockedSidebar]]' show-menu='[[dockedSidebar]]'
route='[[routeTail]]'
></partial-cards> ></partial-cards>
<partial-panel-resolver <partial-panel-resolver
id='panel-resolver' id='panel-resolver'
narrow='[[narrow]]' narrow='[[narrow]]'
hass='[[hass]]' hass='[[hass]]'
route='[[route]]'
show-menu='[[dockedSidebar]]' show-menu='[[dockedSidebar]]'
></partial-panel-resolver> ></partial-panel-resolver>
@ -60,19 +78,16 @@ Polymer({
is: 'home-assistant-main', is: 'home-assistant-main',
properties: { properties: {
hass: { hass: Object,
narrow: Boolean,
route: {
type: Object, type: Object,
value: null, observer: '_routeChanged',
observer: 'hassChanged',
},
narrow: {
type: Boolean,
},
currentPanel: {
type: String,
}, },
routeData: Object,
routeTail: Object,
dockedSidebar: { dockedSidebar: {
type: Boolean, type: Boolean,
@ -86,14 +101,10 @@ Polymer({
'hass-start-voice': 'handleStartVoice', 'hass-start-voice': 'handleStartVoice',
}, },
hassChanged: function (hass) { _routeChanged: function () {
if (this.currentPanel !== hass.currentPanel) {
this.currentPanel = hass.currentPanel;
if (this.narrow) { if (this.narrow) {
this.$.drawer.closeDrawer(); this.$.drawer.closeDrawer();
} }
}
}, },
handleStartVoice: function (ev) { handleStartVoice: function (ev) {
@ -127,5 +138,9 @@ Polymer({
computeDockedSidebar: function (hass) { computeDockedSidebar: function (hass) {
return hass.dockedSidebar; return hass.dockedSidebar;
}, },
_computeSelected: function (routeData) {
return routeData.panel || 'states';
},
}); });
</script> </script>

View File

@ -11,6 +11,7 @@
<link rel="import" href="../../bower_components/app-layout/app-scroll-effects/effects/waterfall.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-header/app-header.html">
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html"> <link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
<link rel='import' href='../../bower_components/app-route/app-route.html'>
<link rel="import" href="../components/ha-menu-button.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-start-voice-button.html">
@ -35,7 +36,12 @@
text-transform: uppercase; text-transform: uppercase;
} }
</style> </style>
<app-route
route="{{route}}"
pattern="/:view"
data="{{routeData}}"
active="{{routeMatch}}"
></app-route>
<app-header-layout has-scrolling-region id='layout'> <app-header-layout has-scrolling-region id='layout'>
<app-header effects="waterfall" condenses fixed slot="header"> <app-header effects="waterfall" condenses fixed slot="header">
<app-toolbar> <app-toolbar>
@ -49,7 +55,7 @@
scrollable scrollable
selected='[[currentView]]' selected='[[currentView]]'
attr-for-selected='data-entity' attr-for-selected='data-entity'
on-iron-select='handleViewSelected' on-iron-activate='handleViewSelected'
> >
<paper-tab <paper-tab
data-entity='' data-entity=''
@ -59,10 +65,19 @@
[[locationName]] [[locationName]]
</template> </template>
<template is='dom-if' if='[[defaultView]]'> <template is='dom-if' if='[[defaultView]]'>
<template is='dom-if' if='[[defaultView.attributes.icon]]'> <template
<iron-icon title$='[[computeStateName(defaultView)]]' icon='[[defaultView.attributes.icon]]'></iron-icon> is='dom-if'
if='[[defaultView.attributes.icon]]'
>
<iron-icon
title$='[[computeStateName(defaultView)]]'
icon='[[defaultView.attributes.icon]]'
></iron-icon>
</template> </template>
<template is='dom-if' if='[[!defaultView.attributes.icon]]'> <template
is='dom-if'
if='[[!defaultView.attributes.icon]]'
>
[[computeStateName(defaultView)]] [[computeStateName(defaultView)]]
</template> </template>
</template> </template>
@ -101,7 +116,6 @@
<template is='dom-repeat' items='[[views]]'> <template is='dom-repeat' items='[[views]]'>
<ha-cards <ha-cards
data-view$='[[item.entity_id]]' data-view$='[[item.entity_id]]'
show-introduction='[[computeShowIntroduction(currentView, introductionLoaded, viewStates)]]'
states='[[viewStates]]' states='[[viewStates]]'
columns='[[_columns]]' columns='[[_columns]]'
hass='[[hass]]' hass='[[hass]]'
@ -144,6 +158,10 @@ Polymer({
value: false, value: false,
}, },
route: Object,
routeData: Object,
routeMatch: Boolean,
_columns: { _columns: {
type: Number, type: Number,
value: 1, value: 1,
@ -162,6 +180,7 @@ Polymer({
currentView: { currentView: {
type: String, type: String,
computed: '_computeCurrentView(routeMatch, routeData)',
}, },
views: { views: {
@ -242,10 +261,20 @@ Polymer({
handleViewSelected: function (ev) { handleViewSelected: function (ev) {
var view = ev.detail.item.getAttribute('data-entity') || null; var view = ev.detail.item.getAttribute('data-entity') || null;
var current = this.currentView || null; var current = this.currentView;
if (view !== current) { if (view !== current) {
this.fire('hass-navigate', { view: view }); var path = this.route.prefix;
if (view) {
path += '/' + view;
} }
history.pushState(null, null, path);
this.fire('location-changed');
}
},
_computeCurrentView: function (routeMatch, routeData) {
return routeMatch ? routeData.view : '';
}, },
computeTitle: function (views, locationName) { computeTitle: function (views, locationName) {
@ -276,12 +305,6 @@ Polymer({
}, },
hassChanged: function (hass) { hassChanged: function (hass) {
var newView = hass.currentView || '';
if (newView !== this.currentView) {
this.currentView = newView;
}
var views = window.HAWS.extractViews(hass.states); var views = window.HAWS.extractViews(hass.states);
// If default view present, it's in first index. // If default view present, it's in first index.
if (views.length > 0 && views[0].entity_id === this.DEFAULT_VIEW_ENTITY_ID) { if (views.length > 0 && views[0].entity_id === this.DEFAULT_VIEW_ENTITY_ID) {

View File

@ -1,4 +1,5 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'> <link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel='import' href='../../bower_components/app-route/app-route.html'>
<link rel="import" href="./hass-loading-screen.html"> <link rel="import" href="./hass-loading-screen.html">
@ -9,6 +10,12 @@
display: none !important; display: none !important;
} }
</style> </style>
<app-route
route="{{route}}"
pattern="/:panel"
data="{{routeData}}"
tail="{{routeTail}}"
></app-route>
<template is='dom-if' if='[[!resolved]]'> <template is='dom-if' if='[[!resolved]]'>
<hass-loading-screen <hass-loading-screen
@ -43,6 +50,15 @@ Polymer({
observer: 'updateAttributes', observer: 'updateAttributes',
}, },
route: Object,
routeData: Object,
routeTail: {
type: Object,
observer: 'updateAttributes',
},
resolved: { resolved: {
type: Boolean, type: Boolean,
value: false, value: false,
@ -55,13 +71,13 @@ Polymer({
panel: { panel: {
type: Object, type: Object,
computed: 'computeCurrentPanel(hass)', computed: 'computeCurrentPanel(hass, routeData)',
observer: 'panelChanged', observer: 'panelChanged',
}, },
}, },
computeCurrentPanel: function (hass) { computeCurrentPanel: function (hass, routeData) {
return hass.config.panels[hass.currentPanel]; return hass.config.panels[routeData.panel];
}, },
panelChanged: function (panel) { panelChanged: function (panel) {
@ -102,6 +118,7 @@ Polymer({
customEl.hass = this.hass; customEl.hass = this.hass;
customEl.narrow = this.narrow; customEl.narrow = this.narrow;
customEl.showMenu = this.showMenu; customEl.showMenu = this.showMenu;
customEl.route = this.routeTail;
}, },
}); });
</script> </script>

View File

@ -1,19 +1,7 @@
<script> <script>
(function () { (function () {
var PAGE_TITLE = 'Home Assistant'; /* eslint-disable no-console */
var DEBUG = false;
function pageState(panel, view) {
var state = { panel: panel };
if (panel === 'states') {
state.view = view || null;
}
return state;
}
function pageUrl(pane, view) {
return pane === 'states' && view ?
'/' + pane + '/' + view : '/' + pane;
}
Polymer({ Polymer({
is: 'ha-url-sync', is: 'ha-url-sync',
@ -26,63 +14,49 @@
}, },
hassChanged: function (newHass, oldHass) { hassChanged: function (newHass, oldHass) {
if (!oldHass) { if (this.ignoreNextHassChange) {
if (DEBUG) console.log('ignore hasschange');
this.ignoreNextHassChange = false;
return; return;
} else if (newHass.currentPanel === oldHass.currentPanel && } else if (!oldHass || oldHass.moreInfoEntityId === newHass.moreInfoEntityId) {
newHass.currentView === oldHass.currentView) { return;
// did the more info entity change? }
if (oldHass.moreInfoEntityId !== newHass.moreInfoEntityId) {
if (newHass.moreInfoEntityId) { if (newHass.moreInfoEntityId) {
// push same state so that back button works. if (DEBUG) console.log('pushing state');
history.pushState(history.state, PAGE_TITLE, window.location.pathname); history.pushState(null, null, window.location.pathname);
} else if (this.ignoreNextDeselectEntity) {
this.ignoreNextDeselectEntity = false;
} else { } else {
if (DEBUG) console.log('history back');
this.ignoreNextPopstate = true;
history.back(); history.back();
} }
}
return;
} else if (this.ignoreNextNav) {
this.ignoreNextNav = false;
return;
}
history.pushState(
pageState(newHass.currentPanel, newHass.currentView), PAGE_TITLE,
pageUrl(newHass.currentPanel, newHass.currentView));
}, },
popstateChangeListener: function (ev) { popstateChangeListener: function (ev) {
if (this.ignoreNextPopstate) {
if (DEBUG) console.log('ignore popstate');
this.ignoreNextPopstate = false;
return;
}
if (DEBUG) console.log('popstate', ev);
if (this.hass.moreInfoEntityId) { if (this.hass.moreInfoEntityId) {
this.ignoreNextDeselectEntity = true; if (DEBUG) console.log('deselect entity');
this.ignoreNextHassChange = true;
this.fire('hass-more-info', { entityId: null }); this.fire('hass-more-info', { entityId: null });
} else if (this.hass.currentPanel !== ev.state.panel ||
this.hass.currentView !== ev.state.view) {
this.ignoreNextNav = true;
this.fire('hass-navigate', ev.state);
} }
}, },
// initial url sync
attached: function () { attached: function () {
this.ignoreNextPopstate = false;
this.ignoreNextHassChange = false;
this.popstateChangeListener = this.popstateChangeListener.bind(this); this.popstateChangeListener = this.popstateChangeListener.bind(this);
// keep state in sync when url changes via forward/back buttons
window.addEventListener('popstate', this.popstateChangeListener); window.addEventListener('popstate', this.popstateChangeListener);
},
// store current view / panel detached: function () {
if (window.location.pathname === '/') { window.removeEventListener('popstate', this.popstateChangeListener);
var currentPanel = this.hass.currentPanel;
var currentView = this.hass.currentView;
history.replaceState(
pageState(currentPanel, currentView), PAGE_TITLE,
pageUrl(currentPanel, currentView));
} else {
var parts = window.location.pathname.substr(1).split('/');
this.fire('hass-navigate', pageState(parts[0], parts[1]));
}
} }
}); });
}()); }());